<?xml version="1.0" encoding="utf-8"?>
<search>
  <entry>
    <title>技嘉GB-BRi5H-8250黑苹果安装指南</title>
    <url>/2020/07/24/GB-BRi5H-8250-hackintosh-guide/</url>
    <content><![CDATA[<h2 id="硬件信息"><a href="#硬件信息" class="headerlink" title="硬件信息"></a>硬件信息</h2><h3 id="GB-BRi5H-8250-准系统"><a href="#GB-BRi5H-8250-准系统" class="headerlink" title="GB-BRi5H-8250 准系统"></a>GB-BRi5H-8250 准系统</h3><p><img data-src="https://image.ponder.work/mweb/2020-07-25-15955608894288.png"></p>
<span id="more"></span>
<p><img data-src="https://image.ponder.work/mweb/2020-07-25-15955609109238.png"><br><img data-src="https://image.ponder.work/mweb/2020-07-25-15955609232489.png"></p>
<table>
<thead>
<tr>
<th>硬件</th>
<th>规格</th>
</tr>
</thead>
<tbody><tr>
<td>尺寸</td>
<td>4‎6.8 mm x 112.6 mm x 119.4 mm (1.84” x 4.43” x 4.7”)</td>
</tr>
<tr>
<td>中央处理器</td>
<td>Intel® Core™ 四核 i5-8250U 3.4GHz</td>
</tr>
<tr>
<td>内存</td>
<td>内建2组SO-DIMM DDR4插槽 2400MHz 最高支持 64GB</td>
</tr>
<tr>
<td>有线网络</td>
<td>内建千兆网卡 (Intel i219V)</td>
</tr>
<tr>
<td>无线网卡</td>
<td>可扩展Intel® Dual Band Wireless-AC 3168</td>
</tr>
<tr>
<td>图形处理器</td>
<td>Intel® UHD Graphics 620</td>
</tr>
<tr>
<td>音频</td>
<td>Realtek ALC255</td>
</tr>
<tr>
<td>HDMI视频输出</td>
<td>HDMI2.0a ws HDCP 2.2 4096 x 2304 @ 60Hz</td>
</tr>
<tr>
<td>MiniDP视频输出</td>
<td>DP1.2a ws HDCP 2.2 4096 x 2304 @ 60Hz</td>
</tr>
<tr>
<td>扩展槽</td>
<td>内建1组M.2 SSD (2280) 插槽 PCIe X4 &#x2F;SATA 支持Intel傲腾内存<br/>1x PCIe M.2 NGFF 2230 A-E key slot 支持 WiFi+BT card</td>
</tr>
<tr>
<td>存储</td>
<td>支持 2.5 英寸 HDD&#x2F;SSD, 7.0&#x2F;9.5 mm (6 Gbps SATA3)</td>
</tr>
</tbody></table>
<h3 id="其他配件"><a href="#其他配件" class="headerlink" title="其他配件"></a>其他配件</h3><ul>
<li>内存：SEIWHALE 枭鲸笔记本内存条 DDR4 16G</li>
<li>硬盘：Samsung 850 EVO M.2 SATA 250G</li>
<li>无线网卡：DW1820A (08PKF4)</li>
</ul>
<h2 id="总体成果"><a href="#总体成果" class="headerlink" title="总体成果"></a>总体成果</h2><p>OpenCore配置文件我放到github了 <a href="https://github.com/ruanimal/GB-BRi5H-8250-hackintosh">https://github.com/ruanimal/GB-BRi5H-8250-hackintosh</a></p>
<h3 id="正常工作项目"><a href="#正常工作项目" class="headerlink" title="正常工作项目"></a>正常工作项目</h3><ul>
<li>CPU，变频</li>
<li>显卡，硬件加速</li>
<li>HDMI输出，MiniDP输出，HDMI音频，双屏</li>
<li>蓝牙</li>
<li>WiFi</li>
<li>Handoff, Airdrop</li>
<li>iMessage, iCloud, Photos, Mac App Store</li>
<li>USB</li>
<li>音频接口，mic</li>
<li>苹果电源管理</li>
<li>睡眠，关机，唤醒</li>
</ul>
<h3 id="不正常工作项目"><a href="#不正常工作项目" class="headerlink" title="不正常工作项目"></a>不正常工作项目</h3><ul>
<li>系统偏好设置-节能（错误识别为有电池）<br>  <img data-src="https://image.ponder.work/mweb/2020-07-25-15956404046858.jpg"></li>
<li>HDMI睡眠唤醒，有时屏幕黑屏，需要按一下屏幕开关；DP睡眠正常 (添加启动参数<code>igfxonln=1</code>应该可以解决)</li>
<li>显示器喇叭开机时不正常，拔插接口后或者开关屏幕电源后正常。(添加启动参数<code>igfxonln=1</code>应该可以解决)</li>
</ul>
<h2 id="安装教程"><a href="#安装教程" class="headerlink" title="安装教程"></a>安装教程</h2><p>安装过程大部分参考<a href="https://dortania.github.io/OpenCore-Install-Guide/">dortania</a>，这里采用的是双系统安装方法。</p>
<p>先安装Windows10, 然后在Windows10下配置OpenCore，制作macOS安装镜像。</p>
<h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>预先下载的工具软件</p>
<ul>
<li><a href="http://www.wepe.com.cn/download.html">微PE工具箱V2.1</a>：Windows系统安装工具</li>
<li><a href="https://www.microsoft.com/zh-cn/software-download/windows10ISO/">Windows10镜像</a></li>
<li><a href="http://www.pc6.com/mac/691696.html">Hackintool</a>: 黑苹果多功能工具</li>
<li><a href="https://www.python.org/downloads/">Python</a>: ProperTree等软件的依赖</li>
<li><a href="https://www.diskgenius.cn/download.php">DiskGenius</a>: 磁盘分区</li>
<li><a href="https://bootice.softonic.cn/?ex=MOB-615.2">bootice</a>: uefi引导编辑</li>
<li><a href="https://github.com/corpnewt/ProperTree">ProperTree</a>: 编辑OpenCore配置</li>
<li><a href="https://github.com/corpnewt/GenSMBIOS">GenSMBIOS</a>：用于生成苹果设备序列号信息</li>
<li><a href="https://github.com/acidanthera/OpenCorePkg/releases">OpenCorePkg</a>: OpenCore引导文件，注意下载release版本</li>
<li><del><a href="https://github.com/corpnewt/gibMacOS">gibMacOS</a>: 下载，制作macOS安装镜像</del></li>
<li><a href="https://www.ccleaner.com/speccy/download">speccy</a>: 查看电脑硬件信息</li>
</ul>
<p>其他</p>
<ul>
<li>能正常访问Google的网络</li>
<li>另外一台电脑，用于查资料（可选）</li>
<li>U盘1-2个</li>
</ul>
<p>前置知识</p>
<ul>
<li><a href="https://dortania.github.io/OpenCore-Install-Guide/terminology.html">技术术语了解</a></li>
</ul>
<h3 id="Windows安装和分区"><a href="#Windows安装和分区" class="headerlink" title="Windows安装和分区"></a>Windows安装和分区</h3><ol>
<li>制作Windows安装U盘</li>
<li>开机安装Delete键进入bios，选择从U盘启动</li>
<li>安装Windows系统，具体安装过程可以参考<a href="https://zhuanlan.zhihu.com/p/49181786">这个教程</a>，注意安装时不要Windows分区不要占用所有空间，建议50G就好了。</li>
<li>安装完成后，新建一个分区，后续作为macOS的安装分区，建议预留100G以上</li>
</ol>
<h3 id="macOS安装U盘制作"><a href="#macOS安装U盘制作" class="headerlink" title="macOS安装U盘制作"></a>macOS安装U盘制作</h3><h4 id="macOS准备"><a href="#macOS准备" class="headerlink" title="macOS准备"></a>macOS准备</h4><p>具体步骤参考<a href="https://dortania.github.io/OpenCore-Install-Guide/installer-guide/winblows-install.html#downloading-macos">这个</a>，gibMacOS已被官方教程废弃<br><del>1. 使用gibMacOS的gibMacOS.bat，下载最新macOS</del><br><del>2. 使用gibMacOS的MakeInstall.bat，制作安装U盘</del></p>
<h4 id="OpenCore文件准备"><a href="#OpenCore文件准备" class="headerlink" title="OpenCore文件准备"></a>OpenCore文件准备</h4><ol>
<li>将下载的OpenCorePkg解压，复制EFI文件夹到U盘</li>
<li>删除EFI文件夹中多余的文件，只保留以下内容<br> <img data-src="https://image.ponder.work/mweb/2020-07-25-15955829462860.png"></li>
<li>添加必要的.efi驱动到U盘EFI&#x2F;OC&#x2F;Drivers文件夹，不同的机型需要的有所区别<ul>
<li><a href="https://github.com/acidanthera/OcBinaryData/blob/master/Drivers/HfsPlus.efi">HfsPlus.efi</a>: 用于读写苹果HFS分区</li>
</ul>
</li>
</ol>
<h4 id="Kexts驱动准备"><a href="#Kexts驱动准备" class="headerlink" title="Kexts驱动准备"></a>Kexts驱动准备</h4><p>添加必要的Kexts驱动到U盘EFI&#x2F;OC&#x2F;Kexts文件夹，不同的机型需要的有所区别</p>
<ul>
<li><a href="https://github.com/acidanthera/VirtualSMC/releases">VirtualSMC</a>：将电脑伪装成苹果设备</li>
<li>SMCProcessor.kext: VirtualSMC附带驱动，用于监控cpu状态</li>
<li>SMCSuperIO：VirtualSMC附带驱动，用于监控风扇状态</li>
<li><a href="https://github.com/acidanthera/Lilu/releases">Lilu</a>: 用于加载音频等驱动</li>
<li><a href="https://github.com/acidanthera/WhateverGreen/releases">WhateverGreen</a>：显卡驱动</li>
<li><a href="https://github.com/acidanthera/AppleALC/releases">AppleALC</a>: 声卡驱动</li>
<li><a href="https://github.com/acidanthera/IntelMausi/releases">IntelMausi</a>: 有线网卡驱动</li>
</ul>
<h4 id="SSDT准备"><a href="#SSDT准备" class="headerlink" title="SSDT准备"></a>SSDT准备</h4><p>添加必要的Kexts驱动到U盘EFI&#x2F;OC&#x2F;ACPI文件夹，不同的机型需要的有所区别</p>
<p><img data-src="https://image.ponder.work/mweb/2020-07-25-15955842564825.jpg" alt="-w828"></p>
<p>从opencore的<a href="https://dortania.github.io/OpenCore-Install-Guide/ktext.html#desktop">文档</a>可以看出，我们至少需要以下几个SSDT</p>
<ul>
<li><a href="https://dortania.github.io/Getting-Started-With-ACPI/Universal/plug.html">SSDT-PLUG</a>：电源管理相关，影响睡眠</li>
<li><a href="https://dortania.github.io/Getting-Started-With-ACPI/Universal/ec-fix.html">SSDT-EC-USBX</a>：内置控制器，和USB相关，影响睡眠</li>
<li><a href="https://dortania.github.io/Getting-Started-With-ACPI/Universal/nvram.html">SSDT-PMC</a>：NVRAM相关</li>
<li><a href="https://dortania.github.io/Getting-Started-With-ACPI/Universal/awac.html">SSDT-AWAC</a>：系统时钟（通过查看DSDT，我们并不需要这个）</li>
</ul>
<p>为了更完美，我们还需要以下SSDT，这些可以等macOS安装完之后，根据情况再考虑是否制作</p>
<ul>
<li><a href="https://dortania.github.io/Getting-Started-With-ACPI/Universal/smbus.html">SSDT-SBUS-MCHC</a>：SMBus相关</li>
<li><a href="https://dortania.github.io/Getting-Started-With-ACPI/Universal/rhub.html">SSDT-RHUB</a>：USB相关</li>
<li><a href="https://dortania.github.io/Getting-Started-With-ACPI/Universal/irq.html">SSDT-HPET</a>：IRQ Conflicts相关</li>
</ul>
<p>具体SSDT的制作过程比较繁琐，详细过程参考<a href="https://dortania.github.io/Getting-Started-With-ACPI/">文档</a></p>
<h4 id="OpenCore配置文件准备"><a href="#OpenCore配置文件准备" class="headerlink" title="OpenCore配置文件准备"></a>OpenCore配置文件准备</h4><ol>
<li>复制OpenCorePKG的Docs&#x2F;Sample.plist到U盘EFI&#x2F;OC&#x2F;config.plist</li>
<li>打开ProperTree的ProperTree.bat脚本，打开前面的config.plist，选择OC Clean Snapshot, 根据我们的驱动文件自动修改配置，保存。<br> <img data-src="https://image.ponder.work/mweb/2020-07-25-15955861876823.jpg" alt="-w355"></li>
<li>配置文件各项微调，参考<a href="https://dortania.github.io/OpenCore-Install-Guide/config.plist/coffee-lake.html">文档</a>，注意DeviceProperties，PlatformInfo配置项，影响比较大。</li>
<li>配置准确性校验 <a href="https://opencore.slowgeek.com/">https://opencore.slowgeek.com/</a></li>
</ol>
<p><strong>注意</strong>：配置文件中kext的顺序是有影响的，建议前两个是lilu和virtualsmc</p>
<h3 id="Bios设置"><a href="#Bios设置" class="headerlink" title="Bios设置"></a>Bios设置</h3><p>如果bios没有相关设置项，可以跳过</p>
<h4 id="禁用项"><a href="#禁用项" class="headerlink" title="禁用项"></a>禁用项</h4><ul>
<li>Fast Boot</li>
<li>Secure Boot</li>
<li>VT-d (can be enabled if you set <code>DisableIoMapper</code> to YES)</li>
<li>CSM</li>
<li>Thunderbolt(For initial install, as Thunderbolt can cause issues if not setup correctly)</li>
<li>Intel SGX</li>
<li>Intel Platform Trust</li>
<li>CFG Lock</li>
</ul>
<h4 id="启用项"><a href="#启用项" class="headerlink" title="启用项"></a>启用项</h4><ul>
<li>VT-x</li>
<li>Above 4G decoding</li>
<li>Hyper-Threading</li>
<li>Execute Disable Bit</li>
<li>EHCI&#x2F;XHCI Hand-off</li>
<li>OS type: Windows 8.1&#x2F;10 UEFI Mode</li>
<li>DVMT Pre-Allocated(iGPU Memory): 64MB</li>
<li>SATA Mode: AHCI</li>
</ul>
<h3 id="macOS安装"><a href="#macOS安装" class="headerlink" title="macOS安装"></a>macOS安装</h3><p><strong>注意</strong>：这时我们先不要替换无线网卡为DW1820A，可能会导致系统安装失败或者卡死。</p>
<ol>
<li>重启电脑，从macOS安装U盘启动</li>
<li>从OpenCore启动菜单选择macOS安装启动项</li>
<li>等待macOS系统加载完成，选择我们预留的分区，格式为APFS, 安装系统</li>
<li>等待多次重启之后，macOS系统初步安装完成</li>
</ol>
<h3 id="驱动安装及优化"><a href="#驱动安装及优化" class="headerlink" title="驱动安装及优化"></a>驱动安装及优化</h3><h4 id="显卡驱动优化"><a href="#显卡驱动优化" class="headerlink" title="显卡驱动优化"></a>显卡驱动优化</h4><p>如果只使用免驱的独立显卡，可以跳过这一步<br>前面配置macOS安装U盘时，其实已经对显卡做了简单驱动，如果一切功能正常，也可以跳过这一步。</p>
<p>显卡驱动不正常会影响双屏显示，睡眠假死，睡眠变重启，HDMI音频输出等</p>
<p>内置显卡的驱动，注意做的是以下几个点</p>
<ul>
<li>选择合适的缓冲帧<code>AAPL,ig-platform-id</code>，必要时仿冒设备id<code>device-id</code></li>
<li>定制正确的显示输出接口，将物理接口和PCI设备正确对应</li>
<li>显存，HDMI相关补丁</li>
</ul>
<p>这一步使用hackintool的应用补丁功能可以完成，详细步骤参考<a href="https://blog.daliansky.net/Tutorial-Using-Hackintool-to-open-the-correct-pose-of-the-8th-generation-core-display-HDMI-or-DVI-output.html">这个</a></p>
<p><strong>注意</strong>：hackintool在配置过程中，有时改动会被重置，应用补丁时注意二次确认</p>
<h4 id="声卡驱动"><a href="#声卡驱动" class="headerlink" title="声卡驱动"></a>声卡驱动</h4><p>声卡驱动大部分工作由AppleALC自动完成，我们需要的是选择正确的layout-id.<br>建议先去github的release日志上查看，是否在某次release添加了你主板的layout-id。如果没有相关记录，那就只能尝试wiki中对应声卡型号的所有可能的layout-id</p>
<p>详细步骤参考<a href="https://dortania.github.io/OpenCore-Post-Install/universal/audio.html">这个</a></p>
<h4 id="USB定制"><a href="#USB定制" class="headerlink" title="USB定制"></a>USB定制</h4><p>USB定制直接影响睡眠是否正常</p>
<p>使用hackintool完成USB定制，主要过程</p>
<ol>
<li>SSDT-EC-USBX定制（前面已经完成）</li>
<li>加载USBInjectAll.kext驱动，重启，注入所有接口。</li>
<li>分别使用USB2.0，USB3.0，Type-C设备拔插所有USB接口，找到物理接口和PCI设备的映射关系，并删除没有用到的USB接口。</li>
<li>生成USBPorts.kext驱动，加载。</li>
</ol>
<p>详细步骤参考<a href="https://blog.daliansky.net/Intel-FB-Patcher-tutorial-and-insertion-pose.html#%E5%AE%9A%E5%88%B6usb">这个</a></p>
<h4 id="DW1820A无线网卡"><a href="#DW1820A无线网卡" class="headerlink" title="DW1820A无线网卡"></a>DW1820A无线网卡</h4><p>无线网卡驱动和系统序列号影响iMessage, sidecar等Apple服务使用。</p>
<ol>
<li>找到无线的PCI设备地址<br> <img data-src="https://image.ponder.work/mweb/2020-07-25-15956404767924.jpg"></li>
<li>在config.plist中<code>DeviceProperties-&gt;Add</code>配置项下加入设备信息, 注意替换PCI设备地址 <figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>PciRoot(0x0)/Pci(0x1C,0x5)/Pci(0x0,0x0)<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>AAPL,slot-name<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">string</span>&gt;</span>WLAN<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>compatible<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">string</span>&gt;</span>pci14e4,43a3<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>device_type<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">string</span>&gt;</span>Airport Extreme<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>model<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">string</span>&gt;</span>DW1820A (BCM4350) 802.11ac Wireless<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>name<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">string</span>&gt;</span>Airport<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">key</span>&gt;</span>pci-aspm-default<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">integer</span>&gt;</span>0<span class="tag">&lt;/<span class="name">integer</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br></pre></td></tr></table></figure></li>
<li>加入Airport驱动<a href="https://github.com/acidanthera/airportbrcmfixup/releases">AirportBrcmFixup.kext</a>, 加入<a href="http://7.daliansky.net/DW1820A/DW1820A_BT_for_Mojave_v2.5.3.zip">蓝牙驱动</a></li>
<li>在config.plist的boot-args项中加入<code>brcmfx-country=#a</code></li>
<li>关机替换网卡为DW1820A（有的机器可能需要屏蔽针脚）</li>
</ol>
<p>详细步骤参考<a href="https://blog.daliansky.net/DW1820A_BCM94350ZAE-driver-inserts-the-correct-posture.html">这个</a>和<a href="https://osxlatitude.com/forums/topic/11322-broadcom-bcm4350-cards-under-high-sierramojavecatalina/?tab=comments#comment-89830">这个</a></p>
<h4 id="电源管理"><a href="#电源管理" class="headerlink" title="电源管理"></a>电源管理</h4><p>这一步是为了启动苹果内置电源管理，影响睡眠</p>
<p>详细步骤参考<a href="https://dortania.github.io/OpenCore-Post-Install/universal/pm.html#enabling-x86platformplugin">这个</a></p>
<h4 id="CPU变频"><a href="#CPU变频" class="headerlink" title="CPU变频"></a>CPU变频</h4><p>使CPU的变频挡位更多，台式机不是特别必要。</p>
<p>可以使用<code>CPU-S</code>软件来查看变频状态</p>
<p>详细步骤参考<a href="https://dortania.github.io/OpenCore-Post-Install/universal/pm.html#using-cpu-friend">这个</a></p>
<h4 id="睡眠修复"><a href="#睡眠修复" class="headerlink" title="睡眠修复"></a>睡眠修复</h4><p>如果上面的SSDT和驱动都做好了，睡眠的问题基本不大了，可能要设置下系统参数，禁止休眠到硬盘。</p>
<p>详细步骤参考<a href="https://dortania.github.io/OpenCore-Post-Install/universal/sleep.html">这个</a></p>
<h4 id="启动项，图形化界面"><a href="#启动项，图形化界面" class="headerlink" title="启动项，图形化界面"></a>启动项，图形化界面</h4><p>到此黑苹果配置基本完成了，我们可以去除<code>boot-args</code>中的<code>-v</code>等debug启动参数，添加相关声音和图片资源，配置config.plist启动OpenCore图形界面。（在开机中启用声音，个人觉得不太不要）</p>
<p>详细步骤参考<a href="https://dortania.github.io/OpenCore-Post-Install/cosmetic/gui.html#setting-up-opencore-s-gui">这个</a></p>
<h3 id="将OpenCore迁移到硬盘"><a href="#将OpenCore迁移到硬盘" class="headerlink" title="将OpenCore迁移到硬盘"></a>将OpenCore迁移到硬盘</h3><p>上面的OpenCore的各种配置都是在U盘上完成的，当配置完成没啥问题之后，我们就可以把OpenCore的文件迁移到硬盘了。<br>这样就不需要每次都从U盘启动了，并且开启会默认进入macOS</p>
<ol>
<li>在pe系统下，使用DiskGenius挂载硬盘ESP分区（右键ESP分区-指派新的驱动器号）<br> <img data-src="https://image.ponder.work/mweb/2020-09-19-16004770939670.jpg" alt="-w1059"></li>
<li>复制U盘的EFI文件夹到ESP分区根目录，和原有的文件夹合并</li>
<li>使用bootice，添加硬盘ESP分区的<code>\EFI\BOOT\BOOTx64.efi</code>文件到启动条目，并上移到第一位<br><img data-src="https://image.ponder.work/mweb/2020-09-19-16004775381948.jpg" alt="-w446"><br><img data-src="https://image.ponder.work/mweb/2020-09-19-16004776841057.jpg" alt="-w548"></li>
</ol>
<h3 id="其他注意事项"><a href="#其他注意事项" class="headerlink" title="其他注意事项"></a>其他注意事项</h3><ul>
<li>每个SSDT基本都可以用手动修改和自动修改，如果某种方法不生效建议尝试另外一种。</li>
<li>如果不是配置完全一样，不要直接使用别人的EFI文件</li>
<li>FakePCIID相关驱动会改动PCI设备信息，可能导致USB设备地址变化，导致USB定制失效，需要重新定制</li>
<li>不同的SMBios机型会影响驱动成功率</li>
<li>修改序列号会影响iCloud账户识别</li>
</ul>
<h2 id="感谢"><a href="#感谢" class="headerlink" title="感谢"></a>感谢</h2><ul>
<li><a href="https://dortania.github.io/">https://dortania.github.io</a></li>
<li><a href="https://github.com/acidanthera">https://github.com/acidanthera</a></li>
<li><a href="https://blog.daliansky.net/">https://blog.daliansky.net</a></li>
<li><a href="https://blog.skk.moe/post/hackintosh-fix-magenta-screen/">https://blog.skk.moe/post/hackintosh-fix-magenta-screen/</a></li>
</ul>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>mac</tag>
        <tag>Hackintosh</tag>
        <tag>电子产品</tag>
      </tags>
  </entry>
  <entry>
    <title>《Getting Things Done》读书笔记</title>
    <url>/2020/05/01/Getting-things-done-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em><br><em>老拖延症企图又又从这本书中获得救赎之道，然而这笔记也拖延了许久。</em></p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Getting Things Done（GTD），是一种时间管理的方法，确切地说是事项管理的方法，通过GTD可以将所有事项管理起来，提高效率的同事，降低心智损耗，减少个人焦虑</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>焦虑是由于缺乏控制力，组织管理，以及缺少准备和行动不足造成的。</p>
<p>目的：</p>
<ol>
<li>收集所有一切需要处理的事，把它们统一置入一个脱离大脑的逻辑系统中</li>
<li>训练自己在接受任何“输入”的前期就作出分析和计划，以确保在任何时候，都拥有一套可以立即执行的计划</li>
</ol>
<p>价值观促使我们首先在清单上积累了大量任务</p>
<p>静水对石子的反应：依物体的质量和力度作出相应的反应，既不过激也不会置之不理</p>
<blockquote>
<p>阮云：物来顺应</p>
</blockquote>
<p>学会对那些承诺的事情加以控制</p>
<p>收集所有那些“经常唤醒你模糊记忆”的事情，然后着手计划如何一一解决掉</p>
<p>在大脑中不留任何事情</p>
<p>自下而上的工作方法</p>
<p>“横向”管理涉及的所有行动</p>
<p>通常你大脑中盘踞问题的数量与其解决效率成反比</p>
<p>横向管理：收集（工作篮） - 加工 - 组织管理 - 检视（至少每周一次） - 执行</p>
<p>确定某一时刻行动的“四标准法”</p>
<ol>
<li>情景</li>
<li>有多少时间</li>
<li>有多少精力</li>
<li>重要性</li>
</ol>
<p>总体检视工作的“六极四顾法”</p>
<ul>
<li>五万英尺+：人生</li>
<li>四万英尺：3-5年的展望</li>
<li>三万英尺：1-2年的目标</li>
<li>二万英尺：责任范围</li>
<li>一万英尺：当前的项目</li>
<li>跑道：目前的行动</li>
</ul>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>拖延症</tag>
      </tags>
  </entry>
  <entry>
    <title>Git 常用命令备忘录</title>
    <url>/2016/01/21/Git%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E5%A4%87%E5%BF%98%E5%BD%95/</url>
    <content><![CDATA[<h2 id="git-简介"><a href="#git-简介" class="headerlink" title="git 简介"></a>git 简介</h2><p>git是一个分散式版本控制软件，最初由林纳斯·托瓦兹（Linus Torvalds）創作，於2005年以GPL釋出。最初目的是为更好地管理Linux内核开发而设计。<br>初始版本由Linus大神在两个星期内写出来，之后基本一统文件版本控制的天下。</p>
<h2 id="名词解释"><a href="#名词解释" class="headerlink" title="名词解释"></a>名词解释</h2><p>工作区：就是你在电脑里能看到的目录。<br>暂存区：英文叫stage, 或index。一般存放在”git目录”下的index文件（.git&#x2F;index）中，所以我们把暂存区有时也叫作索引（index）。<br>版本库：工作区有一个隐藏目录.git，这个不算工作区，而是Git的版本库。<br>branch：分支，相当于不同的平行世界<br>remote：远程仓库<br>origin：默认的远程仓库名词<br>HEAD：一个指向当前版本号的指针</p>
<span id="more"></span>

<h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><p><code>git config --global user.name yourname</code>   设置用户名，如果不加<code>--global</code>则只对当前项目生效<br><code>git config --global user.email youremail</code>    设置邮箱<br><code>git config --global core.editor vim</code>     设置默认编辑器为vim，可替换为你自己喜欢的<br><code>git config --global core.quotepath false</code>   显示中文路径名</p>
<h2 id="开始项目"><a href="#开始项目" class="headerlink" title="开始项目"></a>开始项目</h2><p><code>git init</code>    初始化git仓库<br><code>git clone &lt;repository&gt; [&lt;directory&gt;]</code>    复制项目</p>
<h2 id="管理修改"><a href="#管理修改" class="headerlink" title="管理修改"></a>管理修改</h2><p><code>git status</code>  查看当前仓库状态，绿色为未commit的stage内容，红色为未stage、为添加到版本库内容<br><code>git show &lt;commit-hash-id|tag_name&gt;</code>   查看某次commit的修改内容<br><code>git add filename</code>    保存文件修改到暂存区<br><code>git rm filename</code>     删除工作区的文件和git记录<br><code>git rm --cached filename</code>    删除暂存区的新文件<br><code>git diff</code>    查看工作区和暂存区(stage)的不同<br><code>git diff --cached</code>   比较的是暂存区和版本库的差别<br><code>git diff HEAD</code> 可以查看工作区和版本库的差别<br><code>git diff commit1 commit2</code>    比较不同commit<br><code>git diff commit1 commit2 -- filepath</code> 比较不同commit指定文件更改<br><code>git commit</code>  提交暂存区内容到当前分支，会新加一个commit<br><code>git commit  --amend</code>     追加暂存区内容到当前分支的最近一个commit<br><code>git rebase -i HEAD~4</code>   合并最近4个commit<br><code>git rebase 目标分支名称</code>   把本分支改动放到目标分支之后</p>
<h2 id="分支与Tag"><a href="#分支与Tag" class="headerlink" title="分支与Tag"></a>分支与Tag</h2><p><code>git branch -a</code>   查看所有分支信息<br><code>git branch new_branch_name</code>      新建分支<br><code>git branch -d branch_name</code>      删除分支<br><code>git branch --set-upstream dev origin/dev</code>   指定本地dev分支与远程origin&#x2F;dev分支的链接<br><code>git checkout branch_name</code>      切换到分支头部<br><code>git checkout tags/tag_name</code>      切换到某个tag对应的版本<br><code>git checkout -b new_branch_name</code>    新建分支，并切换到新分支<br><code>git checkout -b dev origin/dev</code>  创建远程origin的dev分支到本地，并切换到新分支<br><code>git merge dev</code> 合并dev到当前分支，保留commit信息<br><code>git merge --squash dev</code>  合并dev分枝代码到暂存区，不保留commit信息<br><code>git tag</code>     查看tag列表<br><code>git tag tag_name</code>    打tag<br><code>git tag -d tag_name</code>     删除tag<br><code>git push --delete origin tagname</code>    删除远程tag</p>
<h2 id="仓库操作"><a href="#仓库操作" class="headerlink" title="仓库操作"></a>仓库操作</h2><p><code>git remote -v</code>   查看远程仓库详细信息<br><code>git remote add &lt;name&gt; &lt;url&gt;</code>     添加远程仓库<br><code>git remote rename &lt;old&gt; &lt;new&gt;</code>   重命名远程仓库<br><code>git remote remove &lt;name&gt;</code>    删除远程仓库<br><code>git remote set-url [--push] &lt;name&gt; &lt;newurl&gt;</code>     设置远程仓库链接<br><code>git remote prune origin</code>     清除本地无用remote<br><code>git pull</code>      &lt;远程主机名&gt; &lt;远程分支名&gt;:&lt;本地分支名&gt;    从远程仓库更新<br><code>git fetch git@github.com:ruanimal/Mysite.git</code>    从其他仓库获取代码，但不更新到本地分支<br><code>git push &lt;远程主机名&gt; &lt;本地分支名&gt;:&lt;远程分支名&gt;</code>    提交到远程仓库<br><code>git push origin &lt;本地分支名&gt;</code>     将本地分支推送与之存在“追踪关系”的远程分支(通常两者同名)<br><code>git push</code>    如果当前分支只有一个追踪分支，那么主机名都可以省略。<br><code>git push -f</code>     强制提交</p>
<h2 id="子模块"><a href="#子模块" class="headerlink" title="子模块"></a>子模块</h2><p><code>git submodule add -b branch {url} [module_name]</code>.  已有仓库，添加子模块<br><code>git submodule set-branch --branch master module_name</code>   子模块切换追踪分支到master<br><code>git submodule update --remote</code> 更新子模块代码, 分支和代码与.gitmodules中的配置同步<br><code>git clone --recursive {url}</code>. clone代码时，拉取子模块代码<br><code>git submodule update --init</code>  克隆项目后, 首次拉取子模块代码<br><code>git rm {submodule_folder}</code>   删除子模块</p>
<h2 id="怎么吃后悔药"><a href="#怎么吃后悔药" class="headerlink" title="怎么吃后悔药"></a>怎么吃后悔药</h2><p><code>git log</code>     查看commit的历史<br><code>git log -p</code>      <filename>查看某个文件的修改历史<br><code>git log -p -2</code>   查看最近2次的更新内容<br><code>git log --graph</code>     命令可以看到分支合并图。<br><code>git whatchanged filename</code>    显示文件更改的相关commit<br><code>git checkout [commit id] -- file</code>      丢弃工作区文件修改<br><code>git restore filename</code>  丢弃工作区文件修改<br><code>git reset HEAD filename</code>    可以把暂存区的修改撤销掉（unstage），重新放回工作区<br><code>git restore --staged filename</code>    可以把暂存区的修改撤销掉（unstage），重新放回工作区<br><code>git reset –mixed</code>      此为默认方式，不带任何参数的git reset，即时这种方式，它回退到某个版本，只保留源码，回退commit和index信息<br><code>git reset –soft</code>       回退到某个版本，只回退了commit的信息，不会恢复到index file一级。如果还要提交，直接commit即可<br><code>git reset –hard</code>       彻底回退到某个版本，本地的源码也会变为上一个版本的内容，若无版本号则回退到最新的版本。<br><code>git reset [commit-id] -- filename</code>   以commit的版本替换stage的文件<br><code>git revert [commit-id]</code>      是用一次新的commit来逆向操作之前的commit</p>
<h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p><code>git gc</code>      压缩历史信息来节约磁盘和内存空间<br><code>git &lt;command&gt; --abort</code>   一般用于中断某次操作<br><code>git cherry-pick &lt;commit_id&gt;</code>       捡取某一个commit到当前分支（包含commit变更的内容和注释）<br><code>git filter-branch --tree-filter &#39;rm -f testme.txt&#39; HEAD</code>       从git仓库中永久删除某个文件的历史<br><code>git rebase -r &lt;commit_id&gt; --exec &#39;git commit --amend --no-edit --reset-author&#39;</code>   将HEAD到commit_id为止的commit的提交用户信息重置</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Git</tag>
      </tags>
  </entry>
  <entry>
    <title>MySQL在django的应用与设置</title>
    <url>/2015/09/10/MySQL%E5%9C%A8django%E7%9A%84%E5%BA%94%E7%94%A8/</url>
    <content><![CDATA[<h2 id="MySQL安装"><a href="#MySQL安装" class="headerlink" title="MySQL安装"></a>MySQL安装</h2><ul>
<li>检查MySQL是否安装 sudo netstat -tap | grep mysql</li>
<li>如果没有 sudo apt-get install mysql-server mysql-client</li>
<li>设置mysql的root用户的密码</li>
</ul>
<h2 id="MySQL的python模块"><a href="#MySQL的python模块" class="headerlink" title="MySQL的python模块"></a>MySQL的python模块</h2><ul>
<li>MySQL-Python</li>
</ul>
<figure class="highlight vim"><table><tr><td class="code"><pre><span class="line">$ wget https://pypi.<span class="keyword">python</span>.org/packages/<span class="keyword">source</span>/M/MySQL-<span class="keyword">python</span>/MySQL-<span class="keyword">python</span>-<span class="number">1.2</span>.<span class="number">5</span>.zip</span><br><span class="line">$ unzip MySQL-<span class="keyword">python</span>-<span class="number">1.2</span>.<span class="number">5</span>.zip</span><br><span class="line">$ sudo <span class="keyword">python</span> setup.<span class="keyword">py</span> install</span><br></pre></td></tr></table></figure>

<ul>
<li>若提示“EnvironmentError: mysql_config not found”<br>  <code>$ sudo apt-get install libmysqld-dev</code></li>
</ul>
<span id="more"></span>

<h2 id="django中的设置"><a href="#django中的设置" class="headerlink" title="django中的设置"></a>django中的设置</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">DATABASES = &#123;</span><br><span class="line">    <span class="string">&#x27;default&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;ENGINE&#x27;</span>: <span class="string">&#x27;django.db.backends.mysql&#x27;</span>, <span class="comment">#设置为mysql数据库</span></span><br><span class="line">        <span class="string">&#x27;NAME&#x27;</span>: <span class="string">&#x27;dmyz&#x27;</span>,  <span class="comment">#mysql数据库名</span></span><br><span class="line">        <span class="string">&#x27;USER&#x27;</span>: <span class="string">&#x27;root&#x27;</span>,  <span class="comment">#mysql用户名，留空则默认为当前linux用户名</span></span><br><span class="line">        <span class="string">&#x27;PASSWORD&#x27;</span>: <span class="string">&#x27;root&#x27;</span>,   <span class="comment">#mysql密码(怀疑有安全性问题)</span></span><br><span class="line">        <span class="string">&#x27;HOST&#x27;</span>: <span class="string">&#x27;&#x27;</span>,  <span class="comment">#留空默认为localhost</span></span><br><span class="line">        <span class="string">&#x27;PORT&#x27;</span>: <span class="string">&#x27;&#x27;</span>,  <span class="comment">#留空默认为3306端口</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>另一种方法是使用mysql配置文件，参看<a href="https://docs.djangoproject.com/en/1.9/ref/databases/#mysql-notes"></a></p>
<h2 id="MySQL-数据库操作"><a href="#MySQL-数据库操作" class="headerlink" title="MySQL 数据库操作"></a>MySQL 数据库操作</h2><ul>
<li>进入mysql命令行 <code>mysql -uroot -p[密码]</code></li>
<li>新建数据库 <code>create database name</code></li>
<li>使用数据库 <code>use name</code></li>
<li>(可选)导入sql备份文件 <code>mysql –uroot –p[密码] -Dtest&lt; .sql</code></li>
</ul>
<h2 id="django初始化数据库"><a href="#django初始化数据库" class="headerlink" title="django初始化数据库"></a>django初始化数据库</h2><ul>
<li>django1.6环境加south</li>
</ul>
<figure class="highlight vim"><table><tr><td class="code"><pre><span class="line"><span class="keyword">python</span> manage.<span class="keyword">py</span> syncdb  # 创建表</span><br><span class="line"><span class="keyword">python</span> manage.<span class="keyword">py</span> migrate  # 数据迁移可选</span><br><span class="line"><span class="keyword">python</span> manage.<span class="keyword">py</span> runserver </span><br></pre></td></tr></table></figure>

<ul>
<li>django1.8+</li>
</ul>
<figure class="highlight vim"><table><tr><td class="code"><pre><span class="line"><span class="keyword">python</span> manage.<span class="keyword">py</span> makemigrations appname  # 建立迁移脚本 </span><br><span class="line"><span class="keyword">python</span> manage.<span class="keyword">py</span> migrate  # 数据迁移可选</span><br><span class="line"><span class="keyword">python</span> manage.<span class="keyword">py</span> runserver </span><br></pre></td></tr></table></figure>



]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Django</tag>
        <tag>MySQL</tag>
      </tags>
  </entry>
  <entry>
    <title>Python2中UnicodeDecodeError的几种解决办法</title>
    <url>/2016/04/16/Python%E4%B8%ADUnicodeDecodeError%E7%9A%84%E5%87%A0%E7%A7%8D%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/</url>
    <content><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>由于在Python2中字符串有两种类型<code>str</code>和<code>unicode</code>，他们都是<code>basestring</code>的子类。</p>
<p>str类型，即是ascii字符或者经过encode的unicode，一个字符占用1byte。ascii码是美国信息交换标准代码，主要用于显示现代英语和其他西欧语言，用一个字节储存一个字符，所以ascii字符最多只有256(2^8)个。</p>
<p>而unicode包含了256个ascii码之外还包含其他各个国家的文字的编码，所以unicode的一个字符占用2个字节，这样理论上一共最多可以表示2^16（即65536）个字符，远大于256。</p>
<p>utf-8是unicode的一中实现，是unicode的一种编码方式。而且utf-8的编码方式，包含了ascii码的内容，也就是utf-8兼容ascii。</p>
<p>打个比方，unicode是商品，utf-8就是打包好的一个个包裹(encode)，打包是为了传输和储存的方便。而不同的编码之间不能直接互相转换，都需要转成unicode，也就是decode。</p>
<p>所以碰到一个str，你就得明白它是encode过的，你得调用相应的decode方法才不会乱码。</p>
<p>python2解释器的默认编码方式是ascii，如果我们给系统的输入是非ascii编码的字符，系统在尝试解码时就会出现<code>UnicodeDecodeError</code></p>
<figure class="highlight python-repl"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">&gt;&gt;&gt;</span> <span class="language-python">s = <span class="string">&#x27;2&#x27;</span></span></span><br><span class="line"><span class="meta prompt_">&gt;&gt;&gt;</span> <span class="language-python">u = <span class="string">u&#x27;2&#x27;</span></span></span><br><span class="line"><span class="meta prompt_">&gt;&gt;&gt;</span> <span class="language-python"><span class="built_in">type</span>(s)</span></span><br><span class="line">&lt;type &#x27;str&#x27;&gt;</span><br><span class="line"><span class="meta prompt_">&gt;&gt;&gt;</span> <span class="language-python"><span class="built_in">type</span>(u)</span></span><br><span class="line">&lt;type &#x27;unicode&#x27;&gt;</span><br><span class="line"><span class="meta prompt_">&gt;&gt;&gt;</span> <span class="language-python"><span class="built_in">issubclass</span>(<span class="built_in">str</span>,basestring)</span></span><br><span class="line"><span class="meta prompt_">&gt;&gt;&gt;</span> <span class="language-python"><span class="built_in">issubclass</span>(unicode,basestring)</span></span><br><span class="line">True</span><br><span class="line"><span class="meta prompt_">&gt;&gt;&gt;</span> <span class="language-python"><span class="string">&#x27;啊&#x27;</span>.decode(<span class="string">&#x27;ascii&#x27;</span>)</span></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;</span><br><span class="line">UnicodeDecodeError: &#x27;ascii&#x27; codec can&#x27;t decode byte 0xe5 in position 0: ordinal not in range(128)</span><br><span class="line">&#x27;ascii&#x27; codec can&#x27;t decode byte 0xe5 in position 0: ordinal not in range(128)</span><br></pre></td></tr></table></figure>

<h2 id="几种解决方法"><a href="#几种解决方法" class="headerlink" title="几种解决方法"></a>几种解决方法</h2><span id="more"></span>

<h3 id="修改系统编码"><a href="#修改系统编码" class="headerlink" title="修改系统编码"></a>修改系统编码</h3><p>这种方法较为普遍</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line">reload(sys)</span><br><span class="line">sys.setdefaultencoding(<span class="string">&#x27;utf-8&#x27;</span>)</span><br></pre></td></tr></table></figure>

<h3 id="使用codec模块"><a href="#使用codec模块" class="headerlink" title="使用codec模块"></a>使用codec模块</h3><p>常用于文本文件操作</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> codecs</span><br><span class="line"><span class="keyword">with</span> codecs.<span class="built_in">open</span>(<span class="string">&#x27;somefile.txt&#x27;</span>, <span class="string">&#x27;r&#x27;</span>, <span class="string">&#x27;utf-8&#x27;</span>) <span class="keyword">as</span> fp:</span><br><span class="line">    <span class="keyword">pass</span></span><br></pre></td></tr></table></figure>

<h3 id="改用Python3"><a href="#改用Python3" class="headerlink" title="改用Python3"></a>改用Python3</h3><p>此乃釜底抽薪，一劳永逸的办法。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>编码</tag>
      </tags>
  </entry>
  <entry>
    <title>Python中re模块的用法</title>
    <url>/2016/05/08/Python%E4%B8%ADre%E6%A8%A1%E5%9D%97%E7%9A%84%E7%94%A8%E6%B3%95/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>re是python中的正则表达式处理模块，本文是为了总结re模块的用法。</p>
<p>至于正则表达式的写法可以看<a href="http://deerchao.net/tutorials/regex/regex.htm">正则表达式30分钟入门教程</a></p>
<h2 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h2><h3 id="re-compile"><a href="#re-compile" class="headerlink" title="re.compile"></a>re.compile</h3><p><code>re.complie(pattern, flags=0)</code><br>把正则编译为<code>_sre.SRE_Pattern object</code>对象，在多次匹配的时候可提高运行效率。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>pattern = re.<span class="built_in">compile</span>(<span class="string">r&#x27;\w(o)&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>pattern.<span class="keyword">match</span>(<span class="string">&#x27;doooo&#x27;</span>)</span><br><span class="line">&lt;_sre.SRE_Match <span class="built_in">object</span> at <span class="number">0x7f45fc3d9990</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>pattern.<span class="keyword">match</span>(<span class="string">&#x27;doooo&#x27;</span>).group(<span class="number">1</span>)</span><br><span class="line"><span class="string">&#x27;o&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.<span class="keyword">match</span>(<span class="string">r&#x27;\w(o)&#x27;</span>, <span class="string">&#x27;doooo&#x27;</span>)</span><br><span class="line">&lt;_sre.SRE_Match <span class="built_in">object</span> at <span class="number">0x7f45ec0be0a8</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.<span class="keyword">match</span>(<span class="string">r&#x27;\w(o)&#x27;</span>, <span class="string">&#x27;doooo&#x27;</span>).group(<span class="number">1</span>)</span><br><span class="line"><span class="string">&#x27;o&#x27;</span></span><br></pre></td></tr></table></figure>
<span id="more"></span>
<h3 id="re-search"><a href="#re-search" class="headerlink" title="re.search"></a>re.search</h3><p><code>re.search(pattern, string, flags=0)</code><br>寻找整个文本，直到发现一个匹配的位置，返回MatchObject，未找到返回None</p>
<h3 id="re-match"><a href="#re-match" class="headerlink" title="re.match"></a>re.match</h3><p><code>re.match(pattern, string, flags=0)</code><br>检查文本的开头是否匹配，返回MatchObject，未找到返回None</p>
<h3 id="re-split"><a href="#re-split" class="headerlink" title="re.split"></a>re.split</h3><p><code>re.split(pattern, string, maxsplit=0, flags=0)</code><br>通过正则表达式分割字符，返回list对象。若表达式里使用了组，组匹配到的内容会加到结果中。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.split(<span class="string">r&#x27;g(o)&#x27;</span>, <span class="string">&#x27;Wo-rds, words, words.&#x27;</span>)</span><br><span class="line">[<span class="string">&#x27;Wo-rds, words, words.&#x27;</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.split(<span class="string">r&#x27;\wo&#x27;</span>, <span class="string">&#x27;Wo-rds, words, words.&#x27;</span>)</span><br><span class="line">[<span class="string">&#x27;&#x27;</span>, <span class="string">&#x27;-rds, &#x27;</span>, <span class="string">&#x27;rds, &#x27;</span>, <span class="string">&#x27;rds.&#x27;</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.split(<span class="string">r&#x27;\w(o)&#x27;</span>, <span class="string">&#x27;Wo-rds, words, words.&#x27;</span>)</span><br><span class="line">[<span class="string">&#x27;&#x27;</span>, <span class="string">&#x27;o&#x27;</span>, <span class="string">&#x27;-rds, &#x27;</span>, <span class="string">&#x27;o&#x27;</span>, <span class="string">&#x27;rds, &#x27;</span>, <span class="string">&#x27;o&#x27;</span>, <span class="string">&#x27;rds.&#x27;</span>]</span><br></pre></td></tr></table></figure>
<h3 id="re-findall"><a href="#re-findall" class="headerlink" title="re.findall"></a>re.findall</h3><p><code>re.findall(pattern, string, flags=0)</code><br>查找所有匹配的对象，返回list。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.findall(<span class="string">r&#x27;g(o)&#x27;</span>, <span class="string">&#x27;Wo-rds, words, words.&#x27;</span>)</span><br><span class="line">[]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.findall(<span class="string">r&#x27;w(o)&#x27;</span>, <span class="string">&#x27;Wo-rds, words, words.&#x27;</span>)</span><br><span class="line">[<span class="string">&#x27;o&#x27;</span>, <span class="string">&#x27;o&#x27;</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.findall(<span class="string">r&#x27;w(o)(r)&#x27;</span>, <span class="string">&#x27;Wo-rds, words, words.&#x27;</span>)</span><br><span class="line">[(<span class="string">&#x27;o&#x27;</span>, <span class="string">&#x27;r&#x27;</span>), (<span class="string">&#x27;o&#x27;</span>, <span class="string">&#x27;r&#x27;</span>)]</span><br></pre></td></tr></table></figure>

<h3 id="re-sub"><a href="#re-sub" class="headerlink" title="re.sub"></a>re.sub</h3><p><code>re.sub(pattern, repl, string, count=0, flags=0</code><br>替换，返回替换后的字符。<br>repl参数可以为一个函数，该函数接受MatchObject，返回值为替换的字符串。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.sub(<span class="string">r&#x27;w(o)(r)&#x27;</span>,<span class="string">r&#x27;*&#x27;</span>, <span class="string">&#x27;Wo-rds, words, words.&#x27;</span>)</span><br><span class="line"><span class="string">&#x27;Wo-rds, *ds, *ds.&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">def</span> <span class="title function_">repl</span>(<span class="params"><span class="keyword">match</span></span>):</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">return</span> <span class="string">&#x27;***%s***|+++%s+++&#x27;</span> % (<span class="keyword">match</span>.group(<span class="number">1</span>), <span class="keyword">match</span>.group(<span class="number">2</span>))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>re.sub(<span class="string">r&#x27;w(o)(r)&#x27;</span>, repl, <span class="string">&#x27;Wo-rds, words, words.&#x27;</span>)</span><br><span class="line"><span class="string">&#x27;Wo-rds, ***o***|+++r+++ds, ***o***|+++r+++ds.&#x27;</span></span><br></pre></td></tr></table></figure>

<h3 id="匹配可选项-flags"><a href="#匹配可选项-flags" class="headerlink" title="匹配可选项 flags"></a>匹配可选项 flags</h3><p>flags是正则表达式匹配的一些选项</p>
<ul>
<li><code>re.DEBUG</code>  输出匹配过程中的调试信息</li>
<li><code>re.IGNORECASE</code>  忽略大小写</li>
<li><code>re.LOCALE</code>  本地化标志</li>
<li><code>re.MULTILINE</code>  多行模式，使<code>^</code>和<code>$</code>匹配每行的行首和行尾</li>
<li><code>re.DOTALL</code>  使<code>.</code>匹配换行符</li>
<li><code>re.UNICODE</code>  使<code>\w</code>等匹配unicode字符</li>
<li><code>re.VERBOSE</code>  使正则表达式使用<code>&quot;&quot;&quot;&quot;&quot;&quot;</code>能够跨多行，并且每行使用<code>#</code>添加注释</li>
</ul>
<h3 id="re-compile对匹配效率的影响"><a href="#re-compile对匹配效率的影响" class="headerlink" title="re.compile对匹配效率的影响"></a>re.compile对匹配效率的影响</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> re, time</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">aa</span>():</span><br><span class="line">    now = time.time()</span><br><span class="line">    string = <span class="string">&#x27;sfsfdsdffffffffffsxcsdsffffffsdsfsffssadfsaasfffffsdssfssssdsdcvfgbdgssdfvdfsss&#x27;</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> xrange(<span class="number">5000000</span>):</span><br><span class="line">        re.<span class="keyword">match</span>(<span class="string">r&#x27;f(s)&#x27;</span>, string)</span><br><span class="line">    seconds = time.time() -now</span><br><span class="line">    <span class="keyword">return</span> seconds</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bb</span>():</span><br><span class="line">    now = time.time()</span><br><span class="line">    string = <span class="string">&#x27;sfsfdsdffffffffffsxcsdsffffffsdsfsffssadfsaasfffffsdssfssssdsdcvfgbdgssdfvdfsss&#x27;</span></span><br><span class="line">    pp = re.<span class="built_in">compile</span>(<span class="string">r&#x27;f(s)&#x27;</span>)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> xrange(<span class="number">5000000</span>):</span><br><span class="line">        re.<span class="keyword">match</span>(pp, string)</span><br><span class="line">    seconds = time.time() -now</span><br><span class="line">    <span class="keyword">return</span> seconds</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cc</span>():</span><br><span class="line">    now = time.time()</span><br><span class="line">    string = <span class="string">&#x27;sfsfdsdffffffffffsxcsdsffffffsdsfsffssadfsaasfffffsdssfssssdsdcvfgbdgssdfvdfsss&#x27;</span></span><br><span class="line">    pp = re.<span class="built_in">compile</span>(<span class="string">r&#x27;f(s)&#x27;</span>)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> xrange(<span class="number">5000000</span>):</span><br><span class="line">        pp.<span class="keyword">match</span>(string)</span><br><span class="line">    seconds = time.time() -now</span><br><span class="line">    <span class="keyword">return</span> seconds</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span> <span class="string">u&#x27;未使用compile: %s&#x27;</span> % aa()</span><br><span class="line"><span class="built_in">print</span> <span class="string">u&#x27;通过re.match调用compile后的对象: %s&#x27;</span> % bb()</span><br><span class="line"><span class="built_in">print</span> <span class="string">u&#x27;使用compile: %s&#x27;</span> % cc()</span><br></pre></td></tr></table></figure>

<p><strong>输出</strong></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">未使用<span class="built_in">compile</span>: <span class="number">5.53200006485</span></span><br><span class="line">通过re.<span class="keyword">match</span>调用<span class="built_in">compile</span>后的对象: <span class="number">11.4990000725</span></span><br><span class="line">使用<span class="built_in">compile</span>: <span class="number">1.66799998283</span></span><br></pre></td></tr></table></figure>

<ul>
<li>可以看出，通过compile过的<code>_sre.SRE_Pattern object</code>对象进行match操作，效率大概比re.mathc高几倍。</li>
<li>而bb函数通过re.match调用compile后的对象，花费时间是未使用compile的两倍。<br>因为<code>re.match(pattern, string)</code>通过调用<code>re._compile(pattern).match(string)</code>来实现的，这样相当于每次匹配都进行了两次compile。</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>正则表达式</tag>
      </tags>
  </entry>
  <entry>
    <title>Python中的类变量与实例变量</title>
    <url>/2016/05/10/Python%E4%B8%AD%E7%9A%84%E7%B1%BB%E5%8F%98%E9%87%8F%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%8F%98%E9%87%8F/</url>
    <content><![CDATA[<p>　　对于初学者来说，python的类变量（也就是java中的静态变量）和实例变量（也就是属性）有一些很容易混淆的地方，同时对这些特性深入了解有助于理解python的面向对象思想。</p>
<h2 id="类变量与实例变量区分"><a href="#类变量与实例变量区分" class="headerlink" title="类变量与实例变量区分"></a>类变量与实例变量区分</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">ClassA</span>(<span class="title class_ inherited__">object</span>):  <span class="comment"># 1</span></span><br><span class="line"><span class="meta">... </span>    num1 = <span class="number">1</span>  <span class="comment"># 类变量</span></span><br><span class="line"><span class="meta">... </span>    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line"><span class="meta">... </span>        <span class="variable language_">self</span>.num2 = <span class="number">2</span>  <span class="comment"># 实例变量，或者说属性</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1 = ClassA()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.num1</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>ClassA.num1</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.num1 <span class="keyword">is</span> ClassA.num1  <span class="comment"># 2</span></span><br><span class="line"><span class="literal">True</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.num1 = <span class="number">3</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.num1 <span class="keyword">is</span> ClassA.num1  <span class="comment"># 3</span></span><br><span class="line"><span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.__class__.num1</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.__class__.num1 = <span class="number">4</span>  <span class="comment"># 4</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>ClassA.num1</span><br><span class="line"><span class="number">4</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a2 = ClassA()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a2.num1</span><br><span class="line"><span class="number">4</span></span><br></pre></td></tr></table></figure>

<ol>
<li>这里我们定义了<code>ClassA</code>类，它有一个类变量<code>num1</code>，还有一个实例变量<code>num2</code>。  </li>
<li>a1是ClassA的一个实例，当我们写下<code>a1.num1</code>的时候，实际上是引用类变量num1，因此<code>a1.num1 is ClassA.num1</code>值为<code>True</code>。  </li>
<li>运行<code>a1.num1 = 3</code>的时候，其实给a1绑定了一个属性num1，这是动态语言的特性，此时<code>a1.num1 is ClassA.num1</code>值为<code>False</code>。<br><strong>因此我不建议通过实例的名称来引用类变量，这样容易引起混淆，你以为改变了ClassA.num1，其实没有。</strong><br>这里就扯到了Python的作用域与命名空间，运行<code>a1.num1</code>的时候，先是在a1自己的命名空间内查找num1，没找到就在所属类的命名空间找，还没有就抛出<code>AttributeError: &#39;ClassA&#39; object has no attribute &#39;num3&#39;</code>.<br>运行<code>a1.num1 = 3</code>之后，a1自己的命名空间内找到了num1，就不继续往上查找了。</li>
<li>如果此时还想通过a1访问和改变类变量num1，可以通过<code>a1.__class__.num1</code>来访问。</li>
</ol>
<p>以上这些，也适用于<code>staticmethod</code>和<code>classmethod</code>。</p>
<h2 id="如果ClassA还有父类"><a href="#如果ClassA还有父类" class="headerlink" title="如果ClassA还有父类"></a>如果ClassA还有父类</h2><span id="more"></span>

<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">FatherI</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta">... </span>    num1 = <span class="number">11</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">FatherII</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta">... </span>    num1 = <span class="number">111</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">ClassA</span>(FatherI, FatherII):</span><br><span class="line"><span class="meta">... </span>    num1 = <span class="number">1</span></span><br><span class="line"><span class="meta">... </span>    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line"><span class="meta">... </span>        <span class="variable language_">self</span>.num2 = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1 = ClassA()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.__class__</span><br><span class="line">&lt;<span class="keyword">class</span> <span class="string">&#x27;ClassA&#x27;</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.__class__.__mro__</span><br><span class="line">(&lt;<span class="keyword">class</span> <span class="string">&#x27;ClassA&#x27;</span>&gt;, &lt;<span class="keyword">class</span> <span class="string">&#x27;FatherI&#x27;</span>&gt;, &lt;<span class="keyword">class</span> <span class="string">&#x27;FatherII&#x27;</span>&gt;, &lt;<span class="built_in">type</span> <span class="string">&#x27;object&#x27;</span>&gt;)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.__class__.__mro__[<span class="number">0</span>].num1</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.__class__.__mro__[<span class="number">1</span>].num1</span><br><span class="line"><span class="number">11</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a1.__class__.__mro__[<span class="number">2</span>].num1</span><br><span class="line"><span class="number">111</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">super</span>(ClassA, a1).num1</span><br><span class="line"><span class="number">11</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">super</span>(ClassA.__mro__[<span class="number">0</span>], a1).num1</span><br><span class="line"><span class="number">11</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">super</span>(FatherI, a1).num1</span><br><span class="line"><span class="number">111</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">super</span>(ClassA.__mro__[<span class="number">1</span>], a1).num1</span><br><span class="line"><span class="number">111</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">super</span>(FatherII, a1).num1</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;&lt;stdin&gt;&quot;</span>, line <span class="number">1</span>, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">AttributeError: <span class="string">&#x27;super&#x27;</span> <span class="built_in">object</span> has no attribute <span class="string">&#x27;num1&#x27;</span></span><br><span class="line"><span class="string">&#x27;super&#x27;</span> <span class="built_in">object</span> has no attribute <span class="string">&#x27;num1&#x27;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>如果ClassA还有父类，而且可能还有多个，想访问父类的类变量可以通过<code>__mro__</code>或者<code>super</code>。</li>
<li><code>__mro__</code>属性显示了一个类的继承树，也就是记录了所有属性和方法的查找顺序，由于此处是新式类，mro为广度优先。<br>由于<code>__mro__</code>是一个元祖，所以我们可以用<code>__mro__[1]</code>这种方式来访问继承树树上每个类的属性和方法。</li>
<li>使用super也能达到相同的效果，不过稍有不同.<code>super(ClassA, a1).num1</code>这个语句的意思其实是，在<code>__mro__</code>中访问<code>&lt;class &#39;ClassA&#39;&gt;</code>后一个类，也就是<code>&lt;class &#39;FatherI&#39;&gt;</code>的num1，所以此处值是11</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python项目中包的导入</title>
    <url>/2016/04/25/Python%E5%8C%85%E5%AF%BC%E5%85%A5/</url>
    <content><![CDATA[<h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">taskman</span><br><span class="line">├── db<span class="selector-class">.sqlite3</span></span><br><span class="line">├── manage<span class="selector-class">.py</span></span><br><span class="line">├── task</span><br><span class="line">│   ├── __init__<span class="selector-class">.py</span></span><br><span class="line">│   ├── admin<span class="selector-class">.py</span></span><br><span class="line">│   ├── models<span class="selector-class">.py</span></span><br><span class="line">│   ├── urls<span class="selector-class">.py</span></span><br><span class="line">│   └── views<span class="selector-class">.py</span></span><br><span class="line">└── man</span><br><span class="line">    ├── __init__<span class="selector-class">.py</span> </span><br><span class="line">    ├── settings<span class="selector-class">.py</span> </span><br><span class="line">    ├── urls<span class="selector-class">.py</span> </span><br><span class="line">    └── wsgi<span class="selector-class">.py</span> </span><br></pre></td></tr></table></figure>

<h2 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h2><p>上面的例子是一个Django项目，Python包的导入都是相对于<code>主程序</code>来说的。</p>
<p>这个项目的主程序就是<code>manage.py</code>，跟manage.py同级的文件夹（含有<code>__init__.py</code>）如<code>task</code>,<code>man</code>就称之为包，若同级有文件就称之为顶级模块。</p>
<p>在这个项目里所有Python包的导入，都应该写完整路径, 或者使用相对当前文件的相对路径。</p>
<p>例如在文件<code>taskman/task/views.py</code>中，想要导入<code>taskman/man/setting.py</code>，就得这样。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">## 绝对路径 import</span></span><br><span class="line"><span class="keyword">from</span> man.setting <span class="keyword">import</span> some_setting</span><br><span class="line"><span class="comment"># or </span></span><br><span class="line"><span class="keyword">from</span> man <span class="keyword">import</span> setting</span><br></pre></td></tr></table></figure>

<p>再例如, 在文件<code>taskman/task/views.py</code>中，想要导入<code>taskman/task/urls.py</code>，就得这样。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">## 绝对路径 import </span></span><br><span class="line"><span class="keyword">from</span> task.urls <span class="keyword">import</span> some_setting</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line"><span class="keyword">from</span> task <span class="keyword">import</span> urls</span><br><span class="line"></span><br><span class="line"><span class="comment">## 相对路径import</span></span><br><span class="line"><span class="keyword">from</span> .urls <span class="keyword">import</span> some_setting</span><br><span class="line"><span class="keyword">from</span> . <span class="keyword">import</span> urls</span><br></pre></td></tr></table></figure>

<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol>
<li>所有导入都是根据主程序的路径，然后从完整路径导入包，或者相应的相对路径导入。</li>
<li>主程序应该位于包的最顶层，否则发生<code>ValueError: Attempted relative import in non-package</code></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>WSL2开启桥接网络</title>
    <url>/2021/01/02/WSL2-bridge-mode/</url>
    <content><![CDATA[<p>Windows Subsystem for Linux(WSL)从<code>Version 1 (WSL1)</code>升级到<code>Version 2 (WSL2)</code> 之后，底层实现方式发生了改变。</p>
<p>由于使用Hyper-V来实现WSL2，使得WSL更像虚拟机，一个能访问本地硬盘的虚拟机。<br>这带来一些便利，能够把它当做独立服务器来使用，可玩性就增强很多。当然，这也导致WSL上的端口不能从外部访问到，总之有利有弊。</p>
<p>虽然能够配置端口转发，曲线救国突破这个缺陷，但是有些服务的端口是约定俗成的（比如samba），更换端口号（原端口号被windows占用）之后其他设备可能识别不到服务。</p>
<p>经过一番思考后，觉得给WSL2开启桥接模式，直接连接物理网络才是相对最好的方案。</p>
<span id="more"></span>

<h2 id="安装WSL2"><a href="#安装WSL2" class="headerlink" title="安装WSL2"></a>安装WSL2</h2><p>参考<a href="https://docs.microsoft.com/zh-cn/windows/wsl/install-win10#manual-installation-steps">官方文档</a>安装WSL2</p>
<p>在PowerShell中执行以下命令</p>
<ol>
<li>启用WSL2 <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart</span><br></pre></td></tr></table></figure></li>
<li>启用虚拟机平台 <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart</span><br></pre></td></tr></table></figure></li>
<li>启用Hyper-V <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">dism.exe /online /enable-feature /featurename:Microsoft-Hyper-V /all /norestart</span><br></pre></td></tr></table></figure></li>
<li>设置WSL2为默认 <figure class="highlight actionscript"><table><tr><td class="code"><pre><span class="line">wsl --<span class="keyword">set</span>-<span class="keyword">default</span>-version <span class="number">2</span></span><br></pre></td></tr></table></figure></li>
</ol>
<p>然后重启系统, 安装<a href="https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi">适用于 x64 计算机的 WSL2 Linux 内核更新包</a></p>
<h2 id="安装Linux发行版"><a href="#安装Linux发行版" class="headerlink" title="安装Linux发行版"></a>安装Linux发行版</h2><p>这里选择Debian作为示例</p>
<ol>
<li>在<code>Microsoft Store</code>中搜索Debian并安装</li>
<li>启动Debian应用，并根据提示初始化系统</li>
<li>在PowerShell执行<code>wsl -l --all -v</code>确认WSL版本为2 <figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">PS C:\Users\ruan&gt; wsl -l <span class="comment">--all -v</span></span><br><span class="line"><span class="type">NAME</span>      STATE           <span class="keyword">VERSION</span></span><br><span class="line">* Debian    Running         <span class="number">2</span></span><br></pre></td></tr></table></figure></li>
<li>如果VERSION不为为2，在PowerShell执行<code>wsl --set-version Debian 2</code>进行升级</li>
</ol>
<h2 id="修改WSL2默认网络为桥接"><a href="#修改WSL2默认网络为桥接" class="headerlink" title="修改WSL2默认网络为桥接"></a>修改WSL2默认网络为桥接</h2><p>由于WSL2底层使用的是Hyper-V虚拟机，所以我们可以修改虚拟交换机的类型，来启用桥接网络<br>打开<code>Hyper-V管理器 -&gt; 操作 -&gt; 虚拟交换机管理器</code>， 修改WSL的连接类型为“外部网络”<br><img data-src="https://image.ponder.work/mweb/2021-01-02-2021-01-02_171339.png" alt="2021-01-02_171339"></p>
<h2 id="修复Debian网络"><a href="#修复Debian网络" class="headerlink" title="修复Debian网络"></a>修复Debian网络</h2><p>由于WSL2默认网络模式是NAT，我们把虚拟交换机改为桥接后，默认的ip和路由以及DNS解析将会失效</p>
<p>以下操作在Debian系统内执行</p>
<h3 id="修复ip和路由"><a href="#修复ip和路由" class="headerlink" title="修复ip和路由"></a>修复ip和路由</h3><ol>
<li>清除原网卡ip <code>ip addr flush dev eth0</code></li>
<li>添加新ip <code>ip addr add 192.168.123.31/24 dev eth0</code></li>
<li>清除原默认路由 <code>ip route delete default</code></li>
<li>添加默认路由 <code>ip route add default via 192.168.123.1 dev eth0</code></li>
</ol>
<p>这些操作在重启WSL2虚拟机后会失效，如果需要永久修改，请配置静态ip</p>
<h3 id="修复DNS解析"><a href="#修复DNS解析" class="headerlink" title="修复DNS解析"></a>修复DNS解析</h3><ol>
<li>新建<code>/etc/wsl.conf</code>，防止WSL2覆盖DNS配置文件 <figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[network]</span></span><br><span class="line"><span class="attr">generateResolvConf</span> = <span class="literal">false</span></span><br></pre></td></tr></table></figure></li>
<li>编辑<code>/etc/resolv.conf</code>， 清除原配置，添加以下内容 <figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">nameserver</span> <span class="number">192.168.123.1</span></span><br></pre></td></tr></table></figure></li>
</ol>
<h2 id="已知缺陷"><a href="#已知缺陷" class="headerlink" title="已知缺陷"></a>已知缺陷</h2><h3 id="重启Windows10后桥接失败"><a href="#重启Windows10后桥接失败" class="headerlink" title="重启Windows10后桥接失败"></a>重启Windows10后桥接失败</h3><p>重启后桥接可能会失败，而且上不了网，可以取消勾选外网网口的“Hyper-V可扩展的虚拟交换机”选项，然后重新配置桥接<br><img data-src="https://image.ponder.work/mweb/2021-01-11-%E6%8D%95%E8%8E%B71.png" alt="捕获1"></p>
<h3 id="WSL2的MAC地址不固定"><a href="#WSL2的MAC地址不固定" class="headerlink" title="WSL2的MAC地址不固定"></a>WSL2的MAC地址不固定</h3><p>由于WSL2的MAC地址每次重启后都会变化，所以桥接后DHCP的ip也是非固定的，参考<a href="https://github.com/microsoft/WSL/issues/5352">issue</a>。</p>
<p>目前没有好的解决办法，一些依赖MAC地址的服务，可能会工作不正常。如samba的域名访问。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://docs.microsoft.com/zh-cn/windows/wsl/install-win10#manual-installation-steps">https://docs.microsoft.com/zh-cn/windows/wsl/install-win10#manual-installation-steps</a></li>
<li><a href="https://github.com/microsoft/WSL/issues/4928#issuecomment-646703350">https://github.com/microsoft/WSL/issues/4928#issuecomment-646703350</a></li>
<li><a href="https://github.com/microsoft/WSL/issues/4150#issuecomment-747152240">https://github.com/microsoft/WSL/issues/4150#issuecomment-747152240</a></li>
</ol>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>Linux</tag>
        <tag>WSL</tag>
      </tags>
  </entry>
  <entry>
    <title>【Chrome扩展】谷歌百度一键搜索修改版</title>
    <url>/2015/11/25/%5BChrome%E6%89%A9%E5%B1%95%5D%20%E8%B0%B7%E6%AD%8C%E7%99%BE%E5%BA%A6%E4%B8%80%E9%94%AE%E6%90%9C%E7%B4%A2%E4%BF%AE%E6%94%B9%E7%89%88/</url>
    <content><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>谷歌百度一键搜索， 在百度页面上搜谷歌，在谷歌的页面上搜索百度，无需切换，无需重新输入搜索词。</p>
<p>详细描述：<br>虽然谷歌比较好用，结果也准确，但搜索中文这方面却也弱了点，百度还是有点用。谷歌百度一键搜索， 在谷歌的页面上搜索百度，在百度页面上搜谷歌，无需切换，无需重新输入搜索词。<br>目前支持http(s):&#x2F;&#x2F;<a href="http://www.baidu.com/">www.baidu.com</a>, http(s):&#x2F;&#x2F;<a href="http://www.google.com.hk/">www.google.com.hk</a>, http(s):&#x2F;&#x2F;<a href="http://www.google.com/">www.google.com</a></p>
<p>创建：2015.11.25<br>作者：ponder.work<br>forked from: raywill&#x2F;BaiGoogleDu</p>
<span id="more"></span>

<h2 id="安装方法"><a href="#安装方法" class="headerlink" title="安装方法"></a>安装方法</h2><p>下载crx文件：<a href="https://github.com/ruanimal/BaiGoogleDu/releases">github</a><br>拖入到 chrome:&#x2F;&#x2F;extensions&#x2F; 页面，即可完成安装。</p>
<h2 id="更新日志"><a href="#更新日志" class="headerlink" title="更新日志"></a>更新日志</h2><p>2015年11月28日　　版本: 1.2.1</p>
<ul>
<li>改进百度首页判断逻辑。</li>
</ul>
<p>2016年12月12日　　版本: 1.2.2</p>
<ul>
<li>适应新版google首页。</li>
</ul>
<h2 id="截图"><a href="#截图" class="headerlink" title="截图"></a>截图</h2><p><img data-src="https://image.ponder.work/mweb/2020-04-30-15882559454860.png"><br><img data-src="https://image.ponder.work/mweb/2020-04-30-15882559641017.png"><br><img data-src="https://image.ponder.work/mweb/2020-04-30-15882559823254.png"></p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>浏览器扩展</tag>
      </tags>
  </entry>
  <entry>
    <title>【软件】下载链接提取器</title>
    <url>/2015/07/25/%5B%E8%BD%AF%E4%BB%B6%5D%E4%B8%8B%E8%BD%BD%E9%93%BE%E6%8E%A5%E6%8F%90%E5%8F%96%E5%99%A8/</url>
    <content><![CDATA[<h2 id="软件简介"><a href="#软件简介" class="headerlink" title="软件简介"></a>软件简介</h2><p>提取文本中的电驴、磁力、迅雷、旋风、快车链接。也可作为一个简易的文本编辑器。<br>功能采用python和正则表达式实现，界面采用wxPython类库，并通过py2exe打包。</p>
<h2 id="使用说明"><a href="#使用说明" class="headerlink" title="使用说明"></a>使用说明</h2><ol>
<li>粘贴文本到程序窗口，或打开txt文件，或者打开网址。</li>
<li>点击相应的按钮实现相应的功能</li>
<li>复制或者保存结果到文件，默认路径问程序当前目录（否则请使用完整路径）</li>
<li>请保证当前路径为英文</li>
</ol>
<h2 id="下载链接"><a href="#下载链接" class="headerlink" title="下载链接"></a>下载链接</h2><p><a href="http://pan.baidu.com/s/1sjP1KBj">http://pan.baidu.com/s/1sjP1KBj</a></p>
<h2 id="更新日志"><a href="#更新日志" class="headerlink" title="更新日志"></a>更新日志</h2><ul>
<li><p>2015.09.13          V2.4<br>  增加直接提取网址源码功能</p>
</li>
<li><p>2015.09.12          V2.3<br>  修正不能正确提取部分迅雷链接</p>
</li>
</ul>
<h2 id="声明"><a href="#声明" class="headerlink" title="声明"></a>声明</h2><p>这个小工具是本人Python的一个习作，水平有限，如有bug可以在这里留言，或者给我发邮件<a href="mailto:&#97;&#x64;&#109;&#x69;&#x6e;&#64;&#x70;&#111;&#110;&#x64;&#x65;&#114;&#46;&#x77;&#x6f;&#x72;&#x6b;">admin@ponder.work</a>。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>在 iOS 上访问安卓应用</title>
    <url>/2025/04/26/access-android-apps-on-ios/</url>
    <content><![CDATA[<p>有些应用在安卓上是独占的，iOS 上又没有比较好的替代品，而且 iOS 上没有能用安卓模拟器。<br>如果使用多个设备，维护的心智成本又高，被这个问题困扰了许久。</p>
<p>最近碰巧了解了 <a href="https://github.com/Genymobile/scrcpy">scrcpy</a>, 用于远程控制安卓，终于解决了这个问题。</p>
<span id="more"></span>

<p>需要的工具</p>
<ol>
<li>安卓环境：虚拟环境或者物理设备均可，这里使用虚拟环境。以下是可选的虚拟环境</li>
</ol>
<ul>
<li>PVE 的 PCT容器 (Proxmox Container Toolkit)，需要打<a href="https://github.com/lurenJBD/PCT-patches">补丁</a></li>
<li>docker，基于<a href="https://github.com/remote-android/redroid-doc">Redroid</a> 需要调整内核参数</li>
<li>WSA (Windows Subsystem for Android)，与 scrcpy 配合有点问题，窗口有黑边。</li>
<li>VMware、VirtualBox等虚拟化平台，需要解决虚拟显卡。</li>
</ul>
<ol start="2">
<li>使用 <a href="https://github.com/wsvn53/scrcpy-mobile">scrcpy-mobile</a> 远程控制安卓环境, 当然 scrcpy 也支持 Windows、Linux、macOS</li>
</ol>
<h2 id="PCT-安卓容器"><a href="#PCT-安卓容器" class="headerlink" title="PCT 安卓容器"></a>PCT 安卓容器</h2><p>由于已有PVE环境，这里选用PCT，主要步骤</p>
<ol>
<li>根据 <a href="https://github.com/lurenJBD/PCT-patches">PCT-patches</a> 文档，给 PCT 打好补丁</li>
<li>根据 <a href="https://github.com/lurenJBD/PCT-patches/releases/download/lineage/lineage19.1-x86_64-houdini-magisk-gapps.tar.gz">lineageOS 模板</a>，新建安卓容器，注意去除Unprivileged container的勾选。</li>
<li>修改 lxc.init.cmd 选项，在后面增加以下参数，调整分辨率和iPad一致。 <figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">androidboot.<span class="attribute">redroid_width</span>=1668 androidboot.<span class="attribute">redroid_height</span>=2388 androidboot.<span class="attribute">redroid_fps</span>=60</span><br></pre></td></tr></table></figure></li>
</ol>
<p>ps: 安卓容器对宿主机性能似乎有一定要求，J4125 只是勉强够用，流畅度一般。</p>
<h2 id="macOS-中使用-scrcpy"><a href="#macOS-中使用-scrcpy" class="headerlink" title="macOS 中使用 scrcpy"></a>macOS 中使用 scrcpy</h2><p>安装 <a href="https://github.com/Genymobile/scrcpy">scrcpy</a> ，连接命令</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">scrcpy <span class="attribute">--audio-codec</span>=aac \</span><br><span class="line">  <span class="attribute">--video-codec</span>=h264 \</span><br><span class="line">  <span class="attribute">--video-bit-rate</span>=16M \</span><br><span class="line">  <span class="attribute">--max-fps</span>=60 \</span><br><span class="line">  <span class="attribute">--tcpip</span>=192.168.10.181:5555 \</span><br><span class="line">  <span class="attribute">--start-app</span>=io.legado.app.release</span><br></pre></td></tr></table></figure>

<p>参数解释</p>
<ul>
<li><code>--audio-codec=aac</code>	同步声音，使用 AAC 编码，默认参数可能导致没有声音</li>
<li><code>--video-codec=h264</code>	使用 H.264 视频压缩编码</li>
<li><code>--video-bit-rate=16M</code>	16Mbps 码率，高画质</li>
<li><code>--max-fps=60</code>	最大帧率 60FPS，画面流畅</li>
<li><code>--tcpip=192.168.10.181:5555</code>	Wi-Fi adb 连接设备</li>
<li><code>--start-app=io.legado.app.release</code> （可选）连接后直接启动 Legado App，应用列表可使用<code>adb shell pm list packages -3</code>查看</li>
</ul>
<h2 id="iOS-中使用-scrcpy-mobile"><a href="#iOS-中使用-scrcpy-mobile" class="headerlink" title="iOS 中使用 scrcpy-mobile"></a>iOS 中使用 scrcpy-mobile</h2><p>appstore中安装 <a href="https://apps.apple.com/cn/app/scrcpy-remote/id1629352527">scrcpy-mobile</a></p>
<p>由于该应用的用户界面易用性比较差，表单也不支持有些 scrcpy 参数，这里直接使用<code>快捷指令</code>打开应用。<br>设置打开 scrcpy-mobile 后，开启<code>引导式访问</code>，防止误触。同时可以在 iOS 设置中开启引导式访问的<code>面容id</code>，防止频繁输入密码。</p>
<p><img data-src="https://image.ponder.work/mweb/2025-04-27---17457324901465.jpg"></p>
<p>scrcpy-mobile 的 url schema</p>
<figure class="highlight sas"><table><tr><td class="code"><pre><span class="line">scrcpy2://192.168.10.181:5555?enable-audio=true<span class="variable">&amp;audio</span>-codec=aac<span class="variable">&amp;video</span>-bit-rate=16M<span class="variable">&amp;video</span>-codec=h264<span class="variable">&amp;max</span>-fps=60</span><br></pre></td></tr></table></figure>

<p>最后，这也未尝不算一种 NTR<br><img data-src="https://image.ponder.work/mweb/2025-04-27---IMG_202504275684_512x682.jpg" alt="IMG_202504275684_512x682"></p>
<h2 id="安卓环境设置（可选）"><a href="#安卓环境设置（可选）" class="headerlink" title="安卓环境设置（可选）"></a>安卓环境设置（可选）</h2><ol>
<li>关闭导航栏：系统 &gt; 手势 &gt; 系统导航 &gt; 手势导航</li>
<li>加快或者关闭系统动画：开发者选项 &gt; 窗口动画缩放、过渡动画缩放、Animator 时长比例 &gt; 关闭或者0.5x</li>
</ol>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>Android</tag>
        <tag>iOS</tag>
      </tags>
  </entry>
  <entry>
    <title>给 macOS 词典增加生词本功能</title>
    <url>/2022/11/12/add-wordlist-for-macOS-dictionary/</url>
    <content><![CDATA[<p>macOS 系统的自带词典应用非常强大，与其他应用整合很好，快捷取词很方便（command+control+d）。<br>但是美中不足的是缺少生词本功能，查了单词又很容易忘记，对语言学习者来说就有些不便了。</p>
<p>经过本强迫症的探索，终于找到基于 Karabiner-Elements + Automator + Logseq 的完美生词本方案。<br>最后的效果是，快捷键取词的同时记录单词卡片到Logseq对应的笔记。</p>
<span id="more"></span>

<h2 id="词典词库扩充"><a href="#词典词库扩充" class="headerlink" title="词典词库扩充"></a>词典词库扩充</h2><p>参考<a href="https://zhuanlan.zhihu.com/p/433646737">知乎文章</a>安装好《朗道英汉字典5.0》<br>这是为了有个释义简洁的词典，方便后续生成生词本词条</p>
<p><img data-src="https://image.ponder.work/mweb/2022-11-02-16673997943438.jpg"></p>
<h2 id="编写workflow"><a href="#编写workflow" class="headerlink" title="编写workflow"></a>编写workflow</h2><p>使用 macOS 自带应用 Automator（自动操作）编写workflow，将当前鼠标所在位置的文本提取并保存制卡。</p>
<p>首先打开 Automator.app 新建一个 Quick Aciont(快速操作)<br><img data-src="https://image.ponder.work/mweb/2022-11-02-16673999123628.jpg"><br>然后依次拖入“获得词语定义”，“运行Shell脚本”等步骤，并调整如下几个位置的选项。<br><img data-src="https://image.ponder.work/mweb/2022-11-02-16674004811769.jpg"></p>
<p>修改脚本里的代码为如下内容，生词本路径相应替换，并相应位置新建好生词本文件。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> unicode_literals, print_function</span><br><span class="line"><span class="keyword">import</span> sys, os, io, subprocess</span><br><span class="line"></span><br><span class="line">FILE=os.path.expanduser(<span class="string">&quot;~/weiyun_sync/!sync/logseq-note/pages/生词本.md&quot;</span>)</span><br><span class="line">output = []</span><br><span class="line">text = sys.argv[<span class="number">1</span>].decode(<span class="string">&#x27;utf8&#x27;</span>) <span class="keyword">if</span> sys.version_info.major == <span class="number">2</span> <span class="keyword">else</span> sys.argv[<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">lines = [i.strip() <span class="keyword">for</span> i <span class="keyword">in</span> text.splitlines() <span class="keyword">if</span> i.strip()]</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(lines) &lt; <span class="number">2</span>:</span><br><span class="line">    exit(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">word = lines[<span class="number">0</span>]</span><br><span class="line"><span class="keyword">if</span> lines[<span class="number">1</span>][<span class="number">0</span>] == <span class="string">&#x27;*&#x27;</span>:</span><br><span class="line">    output.append(<span class="string">&#x27;- &#123;&#125;\t&#123;&#125;  [[card]]&#x27;</span>.<span class="built_in">format</span>(word, lines[<span class="number">1</span>]))</span><br><span class="line">    lines = lines[<span class="number">2</span>:]</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    output.append(<span class="string">&#x27;- &#123;&#125;\t  [[card]]&#x27;</span>.<span class="built_in">format</span>(word))</span><br><span class="line">    lines = lines[<span class="number">1</span>:]</span><br><span class="line">output.append(<span class="string">&#x27;\t- &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(lines[<span class="number">0</span>]))</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> lines[<span class="number">1</span>:]:</span><br><span class="line">    output.append(<span class="string">&#x27;\t  &#x27;</span> + line)</span><br><span class="line"></span><br><span class="line">old_words = <span class="built_in">set</span>()</span><br><span class="line"><span class="keyword">with</span> io.<span class="built_in">open</span>(FILE, <span class="string">&#x27;r&#x27;</span>, encoding=<span class="string">&#x27;utf8&#x27;</span>) <span class="keyword">as</span> fp:</span><br><span class="line">    <span class="keyword">for</span> line <span class="keyword">in</span> fp:</span><br><span class="line">        parts = line.split()</span><br><span class="line">        <span class="keyword">if</span> line.startswith(<span class="string">&#x27;-&#x27;</span>) <span class="keyword">and</span> <span class="built_in">len</span>(parts) &gt; <span class="number">1</span>:</span><br><span class="line">            old_words.add(parts[<span class="number">1</span>])</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> word <span class="keyword">not</span> <span class="keyword">in</span> old_words:</span><br><span class="line">    <span class="keyword">with</span> io.<span class="built_in">open</span>(FILE, <span class="string">&#x27;a&#x27;</span>, encoding=<span class="string">&#x27;utf8&#x27;</span>) <span class="keyword">as</span> fp:</span><br><span class="line">        fp.write(<span class="string">&#x27;\n&#x27;</span>)</span><br><span class="line">        fp.write(<span class="string">&#x27;\n&#x27;</span>.join(output))</span><br><span class="line">        fp.write(<span class="string">&#x27;\n&#x27;</span>)</span><br><span class="line">    subprocess.check_call([<span class="string">&#x27;osascript&#x27;</span>, <span class="string">&#x27;-e&#x27;</span>, <span class="string">u&#x27;display notification &quot;添加 &#123;&#125;&quot; with title &quot;生词本&quot;&#x27;</span>.<span class="built_in">format</span>(word)])</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    subprocess.check_call([<span class="string">&#x27;osascript&#x27;</span>, <span class="string">&#x27;-e&#x27;</span>, <span class="string">u&#x27;display notification &quot;跳过 &#123;&#125;&quot; with title &quot;生词本&quot;&#x27;</span>.<span class="built_in">format</span>(word)])</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>选择路径保存好 workflow，然后在 <code>键盘 - 快捷键 - 服务</code> 中能看到新建的workflow。<br>为它设置快捷键 <code>command + shift + alt + 1</code><br><img data-src="https://image.ponder.work/mweb/2022-11-02-16674009451584.jpg"></p>
<h2 id="Karabiner-Elements"><a href="#Karabiner-Elements" class="headerlink" title="Karabiner-Elements"></a>Karabiner-Elements</h2><p><a href="https://karabiner-elements.pqrs.org/">Karabiner-Elements</a> 是 macOS 平台的一个重新映射快捷键的软件。<br>这里我们使用它将“查询单词”和“触发workflow”整合在一起，当然它还支持很多用途，这里就不赘述了。</p>
<p>注意确保Karabiner相关权限，并且设置中下图相关设备是勾选状态</p>
<p><img data-src="https://image.ponder.work/mweb/2022-11-04-16675619005392.jpg"></p>
<p>安装好Karabiner-Elements后，打开它的配置文件<br>路径在 <code>/Users/&lt;用户名&gt;/.config/karabiner/karabiner.json</code></p>
<p>在 <code>profiles -&gt; complex_modifications -&gt; rules</code> 列表中增加一项配置，内容如下。<br>然后保存，Karabiner会自动加载新的配置。</p>
<p>这里是将鼠标的侧键（靠前的）映射为查单词的快捷键，实现一键查词。<br>也可以根据需要更改按键，通过<a href="https://karabiner-elements.pqrs.org/docs/manual/operation/eventviewer/">EventViewer</a>可以查看按键代码，配置文件格式可参考<a href="https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/">官方文档</a></p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Mouse&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;manipulators&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;from&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;pointing_button&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button5&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;to&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;pointing_button&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button1&quot;</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;pointing_button&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button1&quot;</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;key_code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;d&quot;</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">&quot;modifiers&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                        <span class="string">&quot;left_command&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="string">&quot;left_control&quot;</span></span><br><span class="line">                    <span class="punctuation">]</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;key_code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">&quot;modifiers&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                        <span class="string">&quot;left_option&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="string">&quot;left_shift&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="string">&quot;left_command&quot;</span></span><br><span class="line">                    <span class="punctuation">]</span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;basic&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/2022-11-02-16674017788245.jpg"></p>
<h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><p>可以看到 Logseq 中卡片生成的效果<br><img data-src="https://image.ponder.work/mweb/2022-11-02-16674007610777.jpg"><br><img data-src="https://image.ponder.work/mweb/2022-11-02-16674023982597.jpg"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://karabiner-elements.pqrs.org/docs/">https://karabiner-elements.pqrs.org/docs/</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/433646737">https://zhuanlan.zhihu.com/p/433646737</a></li>
<li><a href="https://hectorguo.com/zh/save-words-in-dictionary/">https://hectorguo.com/zh/save-words-in-dictionary/</a></li>
<li><a href="https://github.com/jjgod/mac-dictionary-kit">https://github.com/jjgod/mac-dictionary-kit</a></li>
<li><a href="https://lightcss.com/mac-dictionary/">https://lightcss.com/mac-dictionary/</a></li>
</ul>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>mac</tag>
      </tags>
  </entry>
  <entry>
    <title>bash 语法备忘</title>
    <url>/2021/11/28/bash-cheat-sheet/</url>
    <content><![CDATA[<p>bash 语法作为程序员好像都了解一些，但又缺少体系化学习，需要使用到某些功能时又经常手忙脚乱地查。<br>本文主要参考<a href="https://wangdoc.com/bash/">阮一峰的bash教程</a>，对bash的知识点进行了梳理。<br>本文目的是作为bash的语法备忘录、语法速查表。</p>
<span id="more"></span>
<h2 id="模式扩展"><a href="#模式扩展" class="headerlink" title="模式扩展"></a>模式扩展</h2><p>模式扩展（globbing），类似C语言中的宏展开，我们通常使用的通配符<code>*</code>就是其中之一。</p>
<p>Bash 一共提供八种扩展，前4种为文件扩展，只有文件路径确实存在才会扩展。</p>
<ul>
<li><code>~</code> 波浪线扩展</li>
<li><code>?</code> 问号扩展</li>
<li><code>*</code> 星号扩展</li>
<li><code>[]</code> 方括号扩展</li>
<li><code>{}</code> 大括号扩展</li>
<li><code>$var</code> 变量扩展</li>
<li><code>$(date)</code> 命令扩展</li>
<li><code>$((1 + 1))</code> 算术扩展</li>
</ul>
<h3 id="波浪线扩展"><a href="#波浪线扩展" class="headerlink" title="波浪线扩展"></a>波浪线扩展</h3><p>波浪线<code>~</code>会自动扩展成当前用户的主目录。<br><code>~user</code>表示扩展成用户<code>user</code>的主目录。如果用户不存在，则波浪号扩展不起作用。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">bash-5.1$ <span class="built_in">echo</span> ~/projects/</span><br><span class="line">/Users/ruan/projects/</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="built_in">echo</span> ~root/.ssh</span><br><span class="line">/var/root/.ssh</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="built_in">echo</span> ~aaa/.ssh</span><br><span class="line">~aaa/.ssh</span><br></pre></td></tr></table></figure>

<h3 id="问号扩展"><a href="#问号扩展" class="headerlink" title="问号扩展"></a>问号扩展</h3><p><code>?</code>字符代表文件路径里面的任意单个字符，不包括空字符。<br><strong>只有文件确实存在的前提下，才会发生扩展。</strong></p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ touch &#123;a,b&#125;.txt ab.txt</span><br><span class="line"></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ ls ?.txt</span><br><span class="line"><span class="attribute">a</span>.txt  b.txt</span><br><span class="line"></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ ls ??.txt</span><br><span class="line"><span class="attribute">ab</span>.txt</span><br></pre></td></tr></table></figure>

<h3 id="星号扩展"><a href="#星号扩展" class="headerlink" title="星号扩展"></a>星号扩展</h3><p><code>*</code>字符代表文件路径里面的任意数量的任意字符，包括零个字符。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">bash-5.1$ <span class="built_in">ls</span> *.txt</span><br><span class="line">a.txt  ab.txt  b.txt</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="built_in">ls</span> /usr/local/Cellar/*/*/bin/z*</span><br><span class="line">/usr/local/Cellar/ffmpeg/4.4_2/bin/zmqsend</span><br><span class="line">/usr/local/Cellar/mysql-client/8.0.26/bin/zlib_decompress</span><br><span class="line">/usr/local/Cellar/netpbm/10.86.24/bin/zeisstopnm</span><br><span class="line">/usr/local/Cellar/perl/5.34.0/bin/zipdetails</span><br><span class="line">/usr/local/Cellar/zstd/1.5.0/bin/zstd</span><br></pre></td></tr></table></figure>

<h3 id="方括号扩展"><a href="#方括号扩展" class="headerlink" title="方括号扩展"></a>方括号扩展</h3><p>方括号扩展的形式是<code>[...]</code>，只有文件确实存在的前提下才会扩展。</p>
<p><code>[^...]</code>和<code>[!...]</code>。它们表示匹配不在方括号里面的字符</p>
<p>方括号扩展有一个简写形式<code>[start-end]</code>，表示匹配一个连续的范围</p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">bash-<span class="number">5.1</span>$ ls <span class="selector-attr">[ab]</span><span class="selector-class">.txt</span></span><br><span class="line"><span class="selector-tag">a</span><span class="selector-class">.txt</span>  <span class="selector-tag">b</span><span class="selector-class">.txt</span></span><br><span class="line"></span><br><span class="line">bash-<span class="number">5.1</span>$ ls <span class="selector-attr">[^b]</span><span class="selector-tag">b</span><span class="selector-class">.txt</span></span><br><span class="line">ab<span class="selector-class">.txt</span></span><br><span class="line"></span><br><span class="line">bash-<span class="number">5.1</span>$ ls <span class="selector-attr">[a-b]</span><span class="selector-class">.txt</span></span><br><span class="line"><span class="selector-tag">a</span><span class="selector-class">.txt</span>  <span class="selector-tag">b</span>.txt</span><br></pre></td></tr></table></figure>

<h3 id="大括号扩展"><a href="#大括号扩展" class="headerlink" title="大括号扩展"></a>大括号扩展</h3><p>大括号扩展<code>{...}</code>表示分别扩展成大括号里面的所有值<br>大括号也可以与其他模式联用，并且总是先于其他模式进行扩展。</p>
<figure class="highlight jboss-cli"><table><tr><td class="code"><pre><span class="line">bash-5.1$ <span class="keyword">echo</span> &#123;1,2,3&#125;</span><br><span class="line">1 2 3</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="keyword">echo</span> a&#123;1,2,3&#125;b</span><br><span class="line">a1b a2b a3b</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="keyword">echo</span> <span class="params">--exclude=</span>&#123;a,b,c&#125;</span><br><span class="line"><span class="params">--exclude=a</span> <span class="params">--exclude=b</span> <span class="params">--exclude=c</span></span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="keyword">echo</span> foo&#123;1,2&#123;1,2&#125;0,3&#125;bar</span><br><span class="line">foo1bar foo210bar foo220bar foo3bar</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="keyword">echo</span> &#123;1.<span class="string">.3</span>&#125;</span><br><span class="line">1 2 3</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="keyword">echo</span> &#123;1.<span class="string">.10..2</span>&#125;</span><br><span class="line">1 3 5 7 9</span><br></pre></td></tr></table></figure>

<h3 id="变量扩展"><a href="#变量扩展" class="headerlink" title="变量扩展"></a>变量扩展</h3><p>Bash 将美元符号<code>$</code>开头的词元视为变量，将其扩展成变量值</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">bash-5.1$ <span class="built_in">echo</span> <span class="variable">$HOME</span></span><br><span class="line">/Users/ruan</span><br></pre></td></tr></table></figure>

<h3 id="命令扩展"><a href="#命令扩展" class="headerlink" title="命令扩展"></a>命令扩展</h3><p><code>$(...)</code>可以扩展成另一个命令的运行结果，该命令的所有输出都会作为返回值。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">bash-5.1$ <span class="built_in">echo</span> $(<span class="built_in">date</span>)</span><br><span class="line">日 11 28 16:22:09 CST 2021</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="built_in">echo</span> `<span class="built_in">date</span>`</span><br><span class="line">日 11 28 16:22:24 CST 2021</span><br></pre></td></tr></table></figure>

<h3 id="算术扩展"><a href="#算术扩展" class="headerlink" title="算术扩展"></a>算术扩展</h3><p><code>$((...))</code>可以扩展成整数运算的结果</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((<span class="number">1</span>+<span class="number">1</span>))</span><br><span class="line"><span class="attribute">2</span></span><br></pre></td></tr></table></figure>

<h2 id="引号使用"><a href="#引号使用" class="headerlink" title="引号使用"></a>引号使用</h2><h3 id="单引号"><a href="#单引号" class="headerlink" title="单引号"></a>单引号</h3><p>单引号用于保留字符的字面含义，在单引号里转义字符和模式扩展都会失效。</p>
<figure class="highlight gams"><table><tr><td class="code"><pre><span class="line">bash<span class="number">-5.1</span><span class="symbol">$</span> ls <span class="string">&#x27;[ab].txt&#x27;</span></span><br><span class="line">ls: cannot access <span class="string">&#x27;[ab].txt&#x27;</span>: <span class="keyword">No</span> such <span class="keyword">file</span> <span class="keyword">or</span> directory</span><br><span class="line"></span><br><span class="line">bash<span class="number">-5.1</span><span class="symbol">$</span> ls <span class="string">&#x27;*&#x27;</span></span><br><span class="line">ls: cannot access <span class="string">&#x27;*&#x27;</span>: <span class="keyword">No</span> such <span class="keyword">file</span> <span class="keyword">or</span> directory</span><br></pre></td></tr></table></figure>

<h3 id="双引号"><a href="#双引号" class="headerlink" title="双引号"></a>双引号</h3><p>双引号比单引号宽松，三个特殊字符除外：美元符号（<code>$</code>）、反引号（<code>`</code>）和反斜杠（<code>\</code>）。这三个字符，会被 Bash 自动扩展。</p>
<p>也就是说，相比单引号在双引号中变量扩展，命令扩展，算术扩展以及转义字符是有效的。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">bash-5.1$ <span class="built_in">echo</span> <span class="string">&quot;<span class="subst">$((1+1)</span>)&quot;</span></span><br><span class="line">2</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$HOME</span>&quot;</span></span><br><span class="line">/Users/ruan</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="built_in">echo</span> <span class="string">&quot;<span class="subst">$(date)</span>&quot;</span></span><br><span class="line">日 11 28 16:35:27 CST 2021</span><br><span class="line"></span><br><span class="line">bash-5.1$ <span class="built_in">echo</span> -e <span class="string">&quot;1\t2&quot;</span></span><br><span class="line">1	2</span><br></pre></td></tr></table></figure>

<h3 id="引号嵌套"><a href="#引号嵌套" class="headerlink" title="引号嵌套"></a>引号嵌套</h3><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 双引号中使用单引号</span></span><br><span class="line">bash<span class="literal">-5</span>.<span class="number">1</span><span class="variable">$</span> <span class="built_in">echo</span> <span class="string">&#x27;&quot;&#x27;</span></span><br><span class="line"><span class="string">&quot;</span></span><br><span class="line"><span class="string">bash-5.1<span class="variable">$</span> echo &#x27;&quot;</span><span class="variable">$</span>&#123;HOME&#125;<span class="string">&quot;&#x27;</span></span><br><span class="line"><span class="string">&quot;</span><span class="variable">$</span>&#123;HOME&#125;<span class="string">&quot;</span></span><br><span class="line"><span class="string"># 单引号中使用双引号</span></span><br><span class="line"><span class="string">bash-5.1<span class="variable">$</span> echo &quot;</span><span class="string">&#x27;$&#123;HOME&#125;&#x27;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">&#x27;/Users/ruan&#x27;</span></span><br><span class="line"><span class="string">bash-5.1<span class="variable">$</span> echo &quot;</span><span class="string">&#x27;\&quot;$&#123;HOME&#125;\&quot;&#x27;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">&#x27;&quot;</span>/Users/ruan<span class="string">&quot;&#x27;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"># 引号嵌套中使用模式扩展，将需要扩展的字符放在单引号中；典型的有json变量填充</span></span><br><span class="line"><span class="string">bash-5.1<span class="variable">$</span> echo &#x27;&#123;&quot;</span>user<span class="string">&quot;: &quot;</span><span class="string">&#x27;$&#123;USER&#125;&#x27;</span><span class="string">&quot;&#125;&#x27;</span></span><br><span class="line"><span class="string">&#123;&quot;</span>user<span class="string">&quot;: &quot;</span>ruan<span class="string">&quot;&#125;</span></span><br></pre></td></tr></table></figure>

<h3 id="here-doc"><a href="#here-doc" class="headerlink" title="here doc"></a>here doc</h3><p>Here 文档（here document）是一种输入多行字符串的方法，格式如下。<br>它的格式分成开始标记（<code>&lt;&lt; token</code>）和结束标记（<code>token</code>）, 一般用字符串<code>EOF</code>作为token</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">&lt;&lt; token</span><br><span class="line">text</span><br><span class="line">token</span><br></pre></td></tr></table></figure>

<p>例如</p>
<figure class="highlight node-repl"><table><tr><td class="code"><pre><span class="line">bash-5.1$ cat &lt;&lt; EOF</span><br><span class="line"><span class="meta prompt_">&gt;</span> <span class="language-javascript"><span class="number">11</span></span></span><br><span class="line"><span class="meta prompt_">&gt;</span> <span class="language-javascript"><span class="number">22</span></span></span><br><span class="line"><span class="meta prompt_">&gt;</span> <span class="language-javascript"><span class="number">33</span></span></span><br><span class="line"><span class="meta prompt_">&gt;</span> <span class="language-javascript"><span class="variable constant_">EOF</span></span></span><br><span class="line">11</span><br><span class="line">22</span><br><span class="line">33</span><br></pre></td></tr></table></figure>

<h3 id="here-string"><a href="#here-string" class="headerlink" title="here string"></a>here string</h3><p>Here 文档还有一个变体，叫做 Here 字符串（Here string），使用三个小于号（<code>&lt;&lt;&lt;</code>）表示。<br>它的作用是将字符串通过标准输入，传递给命令。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">bash-5.1$ <span class="built_in">cat</span> &lt;&lt;&lt; <span class="string">foobar</span></span><br><span class="line"><span class="string">foobar</span></span><br><span class="line">bash-5.1$ <span class="built_in">echo</span> foobar | <span class="built_in">cat</span></span><br><span class="line">foobar</span><br></pre></td></tr></table></figure>

<h2 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h2><p>bash 是基于标准输入在不同进程间交互数据的，大部分功能都是在操作字符串，所以<strong>变量的默认类型也是字符串</strong>。</p>
<h3 id="声明变量和读取变量"><a href="#声明变量和读取变量" class="headerlink" title="声明变量和读取变量"></a>声明变量和读取变量</h3><p>声明时等号两边不能有空格。<br>Bash 变量名区分大小写，<code>HOME</code>和<code>home</code>是两个不同的变量。</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ foo=<span class="number">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $foo</span><br><span class="line"><span class="attribute">1</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="变量查看和删除"><a href="#变量查看和删除" class="headerlink" title="变量查看和删除"></a>变量查看和删除</h3><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看所有变量, 其中包含父进程export的变量</span></span><br><span class="line">bash-5.1$ <span class="built_in">set</span></span><br><span class="line"><span class="attribute">UID</span>=501</span><br><span class="line"><span class="attribute">USER</span>=ruan</span><br><span class="line"><span class="attribute">bar</span>=2</span><br><span class="line"><span class="attribute">foo</span>=1</span><br><span class="line"></span><br><span class="line">bash-5.1$ unset foo</span><br><span class="line">bash-5.1$ <span class="built_in">set</span></span><br><span class="line"><span class="attribute">UID</span>=501</span><br><span class="line"><span class="attribute">USER</span>=ruan</span><br><span class="line"><span class="attribute">bar</span>=2</span><br></pre></td></tr></table></figure>

<h3 id="变量输出"><a href="#变量输出" class="headerlink" title="变量输出"></a>变量输出</h3><p>用户创建的变量仅可用于当前 Shell，子 Shell 默认读取不到父 Shell 定义的变量。<br>如果希望子进程能够读到这个变量，需要使用export命令。</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">bash-5.1$ bash -c <span class="built_in">set</span>  | grep foo</span><br><span class="line">bash-5.1$ <span class="built_in">export</span> <span class="attribute">foo</span>=1</span><br><span class="line">bash-5.1$ bash -c <span class="built_in">set</span>  | grep foo</span><br><span class="line"><span class="attribute">foo</span>=1</span><br></pre></td></tr></table></figure>

<h3 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h3><p>平时所说的环境变量，就是init进程export输出的。子进程对变量的修改不会影响父进程，也就是说变量不是共享的。</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看环境变量</span></span><br><span class="line">bash-5.1$ env</span><br><span class="line"><span class="attribute">SHELL</span>=/bin/zsh</span><br><span class="line"><span class="attribute">LSCOLORS</span>=Gxfxcxdxbxegedabagacad</span><br><span class="line"><span class="attribute">ITERM_PROFILE</span>=Default</span><br></pre></td></tr></table></figure>

<p>下面是一些常见的环境变量。</p>
<ul>
<li><code>BASHPID</code>：Bash 进程的进程 ID。</li>
<li><code>BASHOPTS</code>：当前 Shell 的参数，可以用<code>shopt</code>命令修改。</li>
<li><code>DISPLAY</code>：图形环境的显示器名字，通常是<code>:0</code>，表示 X Server 的第一个显示器。</li>
<li><code>EDITOR</code>：默认的文本编辑器。</li>
<li><code>HOME</code>：用户的主目录。</li>
<li><code>HOST</code>：当前主机的名称。</li>
<li><code>IFS</code>：词与词之间的分隔符，默认为空格。</li>
<li><code>LANG</code>：字符集以及语言编码，比如<code>zh_CN.UTF-8</code>。</li>
<li><code>PATH</code>：由冒号分开的目录列表，当输入可执行程序名后，会搜索这个目录列表。</li>
<li><code>PS1</code>：Shell 提示符。</li>
<li><code>PS2</code>： 输入多行命令时，次要的 Shell 提示符。</li>
<li><code>PWD</code>：当前工作目录。</li>
<li><code>RANDOM</code>：返回一个0到32767之间的随机数。</li>
<li><code>SHELL</code>：Shell 的名字。</li>
<li><code>SHELLOPTS</code>：启动当前 Shell 的<code>set</code>命令的参数</li>
<li><code>TERM</code>：终端类型名，即终端仿真器所用的协议。</li>
<li><code>UID</code>：当前用户的 ID 编号。</li>
<li><code>USER</code>：当前用户的用户名。</li>
</ul>
<h3 id="特殊变量"><a href="#特殊变量" class="headerlink" title="特殊变量"></a>特殊变量</h3><p>Bash 提供一些特殊变量。这些变量的值由 Shell 提供，用户不能进行赋值。</p>
<ul>
<li><code>$?</code>: 上一个命令的退出码, 0为成功，其他为失败</li>
<li><code>$$</code>: 当前进程的pid</li>
<li><code>$_</code>: 为上一个命令的最后一个参数</li>
<li><code>$!</code>: 为最近一个后台执行的异步命令的进程 ID。</li>
<li><code>$0</code>: bash脚本的参数列表，0是脚本文件路径，1到n是第1到第n个参数</li>
</ul>
<h3 id="变量默认值"><a href="#变量默认值" class="headerlink" title="变量默认值"></a>变量默认值</h3><ul>
<li><code>${varname:-word}</code>: 如果变量varname存在且不为空，则返回它的值，否则返回word</li>
<li><code>${varname:=word}</code>: 如果变量varname存在且不为空，则返回它的值，否则将它设为word，并且返回word。</li>
<li><code>${varname:+word}</code>: 如果变量名存在且不为空，则返回word，否则返回空值。它的目的是测试变量是否存在。</li>
<li><code>${varname:?message}</code>: 如果变量varname存在且不为空，则返回它的值，否则打印出varname: message，并中断脚本的执行。</li>
</ul>
<h3 id="declare-命令"><a href="#declare-命令" class="headerlink" title="declare 命令"></a>declare 命令</h3><p><code>declare</code>命令的主要参数（OPTION）如下。</p>
<ul>
<li><code>-a</code>：声明数组变量。</li>
<li><code>-A</code>：声明关联数组变量。</li>
<li><code>-f</code>：输出所有函数定义。</li>
<li><code>-F</code>：输出所有函数名。</li>
<li><code>-i</code>：声明整数变量。</li>
<li><code>-p</code>：查看变量信息。</li>
<li><code>-r</code>：声明只读变量。</li>
<li><code>-x</code>：该变量输出为环境变量。</li>
</ul>
<h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><p>bash 有字符串，数字，数字，关联数组四种数据类型，默认是字符串，其他类型需要手动声明。</p>
<h3 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h3><h4 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h4><p>语法 <code>varname=value</code></p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ s1=abcdefg</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $s1</span><br><span class="line"><span class="attribute">abcdefg</span></span><br></pre></td></tr></table></figure>

<h4 id="获取长度（length）"><a href="#获取长度（length）" class="headerlink" title="获取长度（length）"></a>获取长度（length）</h4><p>语法 <code>${#varname}</code></p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;#s1&#125;</span></span><br><span class="line"><span class="attribute">7</span></span><br></pre></td></tr></table></figure>

<h4 id="子字符串（substr）"><a href="#子字符串（substr）" class="headerlink" title="子字符串（substr）"></a>子字符串（substr）</h4><p>语法 <code>${varname:offset:length}</code>, offset为负数的时候，前面要加空格，防止与默认值语法冲突。</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ s1=abcdefg</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;s1:1:3&#125;</span></span><br><span class="line"><span class="attribute">bcd</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;s1: -6:2&#125;</span></span><br><span class="line"><span class="attribute">bc</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;s1: -6:3&#125;</span></span><br><span class="line"><span class="attribute">bcd</span></span><br></pre></td></tr></table></figure>

<h4 id="替换-（replace）"><a href="#替换-（replace）" class="headerlink" title="替换 （replace）"></a>替换 （replace）</h4><h5 id="字符串头部的模式匹配"><a href="#字符串头部的模式匹配" class="headerlink" title="字符串头部的模式匹配"></a>字符串头部的模式匹配</h5><ul>
<li><code>${variable#pattern}</code>: 删除最短匹配（非贪婪匹配）的部分，返回剩余部分</li>
<li><code>${variable##pattern}</code>: 删除最长匹配（贪婪匹配）的部分，返回剩余部分</li>
</ul>
<p>匹配模式pattern可以使用<code>*</code>、<code>?</code>、<code>[]</code>等通配符。</p>
<figure class="highlight crystal"><table><tr><td class="code"><pre><span class="line"><span class="variable">$ </span>myPath=<span class="regexp">/home/cam</span><span class="regexp">/book/long</span>.file.name</span><br><span class="line"></span><br><span class="line"><span class="variable">$ </span>echo <span class="variable">$&#123;</span>myPath<span class="comment">#/*/&#125;</span></span><br><span class="line">cam/book/long.file.name</span><br><span class="line"></span><br><span class="line"><span class="variable">$ </span>echo <span class="variable">$&#123;</span>myPath<span class="comment">##/*/&#125;</span></span><br><span class="line">long.file.name</span><br></pre></td></tr></table></figure>

<h5 id="字符串尾部的模式匹配"><a href="#字符串尾部的模式匹配" class="headerlink" title="字符串尾部的模式匹配"></a>字符串尾部的模式匹配</h5><ul>
<li><code>${variable%pattern}</code>: 删除最短匹配（非贪婪匹配）的部分，返回剩余部分</li>
<li><code>${variable%%pattern}</code>: 删除最长匹配（贪婪匹配）的部分，返回剩余部分</li>
</ul>
<figure class="highlight crystal"><table><tr><td class="code"><pre><span class="line"><span class="variable">$ </span>path=<span class="regexp">/home/cam</span><span class="regexp">/book/long</span>.file.name</span><br><span class="line"></span><br><span class="line"><span class="variable">$ </span>echo <span class="variable">$&#123;</span>path%.*&#125;</span><br><span class="line"><span class="regexp">/home/cam</span><span class="regexp">/book/long</span>.file</span><br><span class="line"></span><br><span class="line"><span class="variable">$ </span>echo <span class="variable">$&#123;</span>path%%.*&#125;</span><br><span class="line"><span class="regexp">/home/cam</span><span class="regexp">/book/long</span></span><br></pre></td></tr></table></figure>

<h5 id="任意位置的模式匹配"><a href="#任意位置的模式匹配" class="headerlink" title="任意位置的模式匹配"></a>任意位置的模式匹配</h5><p>如果匹配<code>pattern</code>则用<code>replace</code>替换匹配的内容</p>
<ul>
<li><code>${variable/pattern/replace}</code>: 替换第一个匹配</li>
<li><code>${variable//pattern/replace}</code>: 替换所有匹配</li>
</ul>
<figure class="highlight crystal"><table><tr><td class="code"><pre><span class="line"><span class="variable">$ </span>path=<span class="regexp">/home/cam</span><span class="regexp">/foo/foo</span>.name</span><br><span class="line"></span><br><span class="line"><span class="variable">$ </span>echo <span class="variable">$&#123;</span>path/foo/bar&#125;</span><br><span class="line"><span class="regexp">/home/cam</span><span class="regexp">/bar/foo</span>.name</span><br><span class="line"></span><br><span class="line"><span class="variable">$ </span>echo <span class="variable">$&#123;</span>path/<span class="regexp">/foo/bar</span>&#125;</span><br><span class="line"><span class="regexp">/home/cam</span><span class="regexp">/bar/bar</span>.name</span><br></pre></td></tr></table></figure>

<h3 id="数字"><a href="#数字" class="headerlink" title="数字"></a>数字</h3><p>使用 <code>declare -i</code>声明整数变量。</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 声明为整数，可以直接计算，不需要使用$符号</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ declare -i val1=<span class="number">12</span> val2=<span class="number">5</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $val1</span><br><span class="line"><span class="attribute">12</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ val1+=val2</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $val1</span><br><span class="line"><span class="attribute">17</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 一个变量声明为整数以后，依然可以被改写为字符串。Bash 不会报错，但会赋以不确定的值</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ val1=aaa</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $val1</span><br><span class="line"><span class="attribute">0</span></span><br></pre></td></tr></table></figure>

<h4 id="数值的进制"><a href="#数值的进制" class="headerlink" title="数值的进制"></a>数值的进制</h4><p>Bash 的数值默认都是十进制，但是在算术表达式中，也可以使用其他进制。</p>
<ul>
<li><code>number</code>：没有任何特殊表示法的数字是十进制数（以10为底）。</li>
<li><code>0number</code>：八进制数。</li>
<li><code>0xnumber</code>：十六进制数。</li>
<li><code>base#number</code>：<code>base</code>进制的数。</li>
</ul>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ declare -i a=<span class="number">0</span>x77</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $a</span><br><span class="line"><span class="attribute">119</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ declare -i a=<span class="number">0</span>xfe</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $a</span><br><span class="line"><span class="attribute">254</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ declare -i a=<span class="number">2</span>#<span class="number">111</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $a</span><br><span class="line"><span class="attribute">7</span></span><br></pre></td></tr></table></figure>

<h4 id="算术表达式"><a href="#算术表达式" class="headerlink" title="算术表达式"></a>算术表达式</h4><p><code>((...))</code>语法可以进行整数的算术运算。<br>支持的算术运算符如下。</p>
<ul>
<li><code>+</code>：加法</li>
<li><code>-</code>：减法</li>
<li><code>*</code>：乘法</li>
<li><code>/</code>：除法（整除）</li>
<li><code>%</code>：余数</li>
<li><code>**</code>：指数</li>
<li><code>++</code>：自增运算（前缀或后缀）</li>
<li><code>--</code>：自减运算（前缀或后缀）</li>
</ul>
<p>如果要读取算术运算的结果，需要在<code>((...))</code>前面加上美元符号<code>$((...))</code>，使其变成算术表达式，返回算术运算的值。</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((<span class="number">1</span>+<span class="number">1</span>))</span><br><span class="line"><span class="attribute">2</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((<span class="number">1</span>-<span class="number">1</span>))</span><br><span class="line"><span class="attribute">0</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((<span class="number">1</span>*<span class="number">2</span>))</span><br><span class="line"><span class="attribute">2</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((<span class="number">1</span>/<span class="number">2</span>))</span><br><span class="line"><span class="attribute">0</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((<span class="number">5</span>%<span class="number">2</span>))</span><br><span class="line"><span class="attribute">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a=<span class="number">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((a++))</span><br><span class="line"><span class="attribute">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((a++))</span><br><span class="line"><span class="attribute">2</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $((++a))</span><br><span class="line"><span class="attribute">4</span></span><br></pre></td></tr></table></figure>

<h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><h4 id="创建数组"><a href="#创建数组" class="headerlink" title="创建数组"></a>创建数组</h4><p><code>array=(item1 item2)</code> 语法可初始化数组，括号内可以换行，多行初始化可以用<code>#</code>注释。</p>
<figure class="highlight crystal"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 直接初始化数组</span></span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>a=(<span class="number">1</span> <span class="number">2</span> <span class="number">3</span>)</span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>echo <span class="variable">$&#123;</span>a[@]&#125;</span><br><span class="line"><span class="number">1</span> <span class="number">2</span> <span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 多行初始化数组</span></span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>a=(</span><br><span class="line">&gt; <span class="number">1</span></span><br><span class="line">&gt; <span class="number">2</span></span><br><span class="line">&gt; <span class="number">3</span></span><br><span class="line">&gt; <span class="comment">#4</span></span><br><span class="line">&gt; )</span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>echo <span class="variable">$&#123;</span>a[@]&#125;</span><br><span class="line"><span class="number">1</span> <span class="number">2</span> <span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 模式扩展初始化</span></span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>a=(&#123;<span class="number">1</span>..<span class="number">3</span>&#125;)</span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>echo <span class="variable">$&#123;</span>a[@]&#125;</span><br><span class="line"><span class="number">1</span> <span class="number">2</span> <span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># declare -a命令声明一个数组，也是可以的。</span></span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>declare -a b</span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>b+=(<span class="number">1</span>)</span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>echo <span class="variable">$&#123;</span>b[@]&#125;</span><br><span class="line"><span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#read -a命令则是将用户的命令行输入，存入一个数组。</span></span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>read -a c</span><br><span class="line"><span class="number">11</span> <span class="number">22</span> <span class="number">33</span></span><br><span class="line">bash-<span class="number">5.1</span><span class="variable">$ </span>echo <span class="variable">$&#123;</span>c[@]&#125;</span><br><span class="line"><span class="number">11</span> <span class="number">22</span> <span class="number">33</span></span><br></pre></td></tr></table></figure>

<h4 id="访问数组元素"><a href="#访问数组元素" class="headerlink" title="访问数组元素"></a>访问数组元素</h4><p><code>array[index]</code> 语法可访问数组元素，不带index访问则是访问数组首个元素。</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a=(<span class="number">1</span> <span class="number">2</span> <span class="number">3</span>)</span><br><span class="line"><span class="comment"># 查看元素</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[1]&#125;</span></span><br><span class="line"><span class="attribute">2</span></span><br><span class="line"><span class="comment"># 元素赋值</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a[<span class="number">1</span>]+=<span class="number">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[1]&#125;</span></span><br><span class="line"><span class="attribute">21</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a[<span class="number">1</span>]=<span class="number">22</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[1]&#125;</span></span><br><span class="line"><span class="attribute">22</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a[<span class="number">0</span>]=<span class="number">1111</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a&#125;</span></span><br><span class="line"><span class="attribute">1111</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[0]&#125;</span></span><br><span class="line"><span class="attribute">1111</span></span><br></pre></td></tr></table></figure>

<h4 id="数组长度"><a href="#数组长度" class="headerlink" title="数组长度"></a>数组长度</h4><p><code>${#array[@]}</code> 和 <code>${#array[*]}</code> 可访问获得数组长度</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a=(<span class="number">1</span> <span class="number">2</span> <span class="number">3</span>)</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;#a[*]&#125;</span></span><br><span class="line"><span class="attribute">3</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;#a[@]&#125;</span></span><br><span class="line"><span class="attribute">3</span></span><br></pre></td></tr></table></figure>

<h4 id="获取非空元素下标"><a href="#获取非空元素下标" class="headerlink" title="获取非空元素下标"></a>获取非空元素下标</h4><p><code>${!array[@]}</code> 或 <code>${!array[*]}</code>, 可以获得非空元素的下标</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a=(<span class="number">1</span> <span class="number">2</span> <span class="number">3</span>)</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a[<span class="number">6</span>]=<span class="number">6</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[0]&#125;</span></span><br><span class="line"><span class="attribute">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]&#125;</span></span><br><span class="line"><span class="attribute">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">6</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[3]&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[4]&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;!a[@]&#125;</span></span><br><span class="line"><span class="attribute">0</span> <span class="number">1</span> <span class="number">2</span> <span class="number">6</span></span><br><span class="line"><span class="comment"># 注意此时数组长度为4，并不是7</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;#a[@]&#125;</span></span><br><span class="line"><span class="attribute">4</span></span><br></pre></td></tr></table></figure>

<h4 id="数组元素切片"><a href="#数组元素切片" class="headerlink" title="数组元素切片"></a>数组元素切片</h4><p><code>${array[@]:position:length}</code>的语法可以提取数组成员。</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a=(&#123;<span class="number">1</span>..<span class="number">10</span>&#125;)</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]&#125;</span></span><br><span class="line"><span class="attribute">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span> <span class="number">7</span> <span class="number">8</span> <span class="number">9</span> <span class="number">10</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]:1:3&#125;</span></span><br><span class="line"><span class="attribute">2</span> <span class="number">3</span> <span class="number">4</span></span><br></pre></td></tr></table></figure>

<h4 id="数组追加元素"><a href="#数组追加元素" class="headerlink" title="数组追加元素"></a>数组追加元素</h4><p>数组末尾追加元素，可以使用<code>+=</code>赋值运算符。</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a=(&#123;<span class="number">1</span>..<span class="number">10</span>&#125;)</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]&#125;</span></span><br><span class="line"><span class="attribute">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span> <span class="number">7</span> <span class="number">8</span> <span class="number">9</span> <span class="number">10</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a+=(<span class="number">11</span> <span class="number">12</span>)</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]&#125;</span></span><br><span class="line"><span class="attribute">1</span> <span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span> <span class="number">7</span> <span class="number">8</span> <span class="number">9</span> <span class="number">10</span> <span class="number">11</span> <span class="number">12</span></span><br></pre></td></tr></table></figure>

<h4 id="删除元素"><a href="#删除元素" class="headerlink" title="删除元素"></a>删除元素</h4><p>删除一个数组成员，使用<code>unset</code>命令。</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a=(<span class="number">1</span> <span class="number">2</span> <span class="number">3</span>)</span><br><span class="line"><span class="comment"># 删除单个元素</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ unset a[<span class="number">1</span>]</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]&#125;</span></span><br><span class="line"><span class="attribute">1</span> <span class="number">3</span></span><br><span class="line"><span class="comment"># 删除整个数组</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ unset a</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]&#125;</span></span><br></pre></td></tr></table></figure>

<h3 id="关联数组"><a href="#关联数组" class="headerlink" title="关联数组"></a>关联数组</h3><p><code>declare -A</code>可以声明关联数组，关联数组使用字符串而不是整数作为数组索引。</p>
<p>除了初始化外，使用方法和数组基本相同</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ declare -A a</span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a[&#x27;red&#x27;]=<span class="number">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ a[&#x27;blue&#x27;]=<span class="number">2</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]&#125;</span></span><br><span class="line"><span class="attribute">2</span> <span class="number">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="variable">$&#123;a[@]:0:1&#125;</span></span><br><span class="line"><span class="attribute">2</span></span><br></pre></td></tr></table></figure>

<h2 id="控制流"><a href="#控制流" class="headerlink" title="控制流"></a>控制流</h2><h3 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h3><p><code>#</code>表示注释，每行从<code>#</code>开始之后的内容代表注释，会被bash忽略.</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo <span class="number">1111</span> # <span class="number">222</span></span><br><span class="line"><span class="attribute">1111</span></span><br></pre></td></tr></table></figure>

<h3 id="条件判断"><a href="#条件判断" class="headerlink" title="条件判断"></a>条件判断</h3><p>bash 和常规编程语言一样使用<code>if</code>作为分支条件的关键字, <code>fi</code>作为结束的关键字，<code>else</code>和<br><code>elif</code>子句是可选的</p>
<p>其中<code>if</code>和<code>elif</code>的<code>condition</code>所判断的内容是命令的<a href="#%E7%8A%B6%E6%80%81%E7%A0%81">状态码</a>是否为0，为0则执行关联的语句。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">因为bash中分号(;)和换行是等价的，所以有下面两种风格，其他多行语句也是类似的</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">本人偏好风格1</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">风格1</span></span><br><span class="line">if condition; then</span><br><span class="line">    command</span><br><span class="line">elif condition; then</span><br><span class="line">    command</span><br><span class="line">else</span><br><span class="line">    command</span><br><span class="line">fi</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">风格2</span></span><br><span class="line">if condition</span><br><span class="line">then</span><br><span class="line">    command</span><br><span class="line">elif condition</span><br><span class="line">then</span><br><span class="line">    command</span><br><span class="line">else</span><br><span class="line">    command</span><br><span class="line">fi</span><br></pre></td></tr></table></figure>

<p>这里的<code>condition</code>可以是多个命令，如<code>command1 &amp;&amp; command2</code>，或者<code>command1 || command2</code>，则if判断的是这两个命令的状态码的逻辑计算结果。</p>
<p><code>condition</code>也是可以是<code>command1; command2</code>， 则则if判断的是最后一个命令的状态码。</p>
<p>这里最常用的<code>condition</code>是<code>test</code>命令, 也就是<code>[[]]</code>和<code>[]</code>. <code>test</code>是bash的内置命令，会执行给定的表达式，结果为真满足则返回状态码0, 否则返回状态码1.</p>
<p>下文循环语言的<code>condition</code>也是相同的，就不赘述了</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ test <span class="number">1</span> -eq <span class="number">1</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $?</span><br><span class="line"><span class="attribute">0</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ test <span class="number">1</span> -eq <span class="number">2</span></span><br><span class="line"><span class="attribute">bash</span>-<span class="number">5</span>.<span class="number">1</span>$ echo $?</span><br><span class="line"><span class="attribute">1</span></span><br></pre></td></tr></table></figure>

<p><code>[[]]</code>和<code>[]</code>的区别是<code>[[]]</code>内部支持<code>&amp;&amp;</code>，<code>||</code>逻辑判断，所以以下三种写法是等价的。</p>
<p>由于<code>[</code>和<code>]</code>是命令， 所以两侧一定要有空格，也是就是<code>[ 1 -eq 1 ]</code>，否则bash会认为命令找不到。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="built_in">test</span></span></span><br><span class="line">if test 1 -eq 2 || test 1 -eq 1; then</span><br><span class="line">    echo True</span><br><span class="line">fi</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">[ ]</span></span><br><span class="line">if [ 1 -eq 2 ] || [ 1 -eq 1 ]; then</span><br><span class="line">    echo True</span><br><span class="line">fi</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">[[ ]]</span></span><br><span class="line">if [[ 1 -eq 2  || 1 -eq 1 ]]; then</span><br><span class="line">    echo True</span><br><span class="line">fi</span><br></pre></td></tr></table></figure>

<h4 id="逻辑操作符"><a href="#逻辑操作符" class="headerlink" title="逻辑操作符"></a>逻辑操作符</h4><p>判断条件支持且（&amp;&amp;）或（||）非（!）</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">not</span>  </span><br><span class="line">if [[ ! &#x27;aa&#x27; == &#x27;bb&#x27; ]]; then</span><br><span class="line">    echo True</span><br><span class="line">fi</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">or</span> </span><br><span class="line">if [[ 1 -eq 2  || 1 -eq 1 ]]; then</span><br><span class="line">    echo True</span><br><span class="line">fi</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">and</span></span><br><span class="line">if [[ 1 -ne 2  &amp;&amp; 1 -eq 1 ]]; then</span><br><span class="line">    echo True</span><br><span class="line">fi</span><br></pre></td></tr></table></figure>

<h4 id="判断时引号使用（quote）"><a href="#判断时引号使用（quote）" class="headerlink" title="判断时引号使用（quote）"></a>判断时引号使用（quote）</h4><p>使用<code>[</code>和<code>test</code>时，变量引用注意加双引号，否则得不到正确的结果，<code>[[</code>则不需要。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">bash-3.2$ echo &quot;$SSH_CLIENT&quot;</span><br><span class="line"></span><br><span class="line">bash-3.2$ if [ -n $SSH_CLIENT ]; then echo 1; else echo 0; fi</span><br><span class="line">1</span><br><span class="line">bash-3.2$ if [ -n &quot;$SSH_CLIENT&quot; ]; then echo 1; else echo 0; fi</span><br><span class="line">0</span><br><span class="line">bash-3.2$ if [[ -n $SSH_CLIENT ]]; then echo 1; else echo 0; fi</span><br><span class="line">0</span><br></pre></td></tr></table></figure>

<h4 id="字符串判断"><a href="#字符串判断" class="headerlink" title="字符串判断"></a>字符串判断</h4><p>bash默认数据类型为字符串，所以常见的 <code>&gt;</code>, <code>&lt;</code>是用于字符串判断。</p>
<p>注意：字符串判断不支持<code>&gt;=</code>和<code>&lt;=</code>, 得使用逻辑组合来替代</p>
<ul>
<li><code>-z string</code>：字符串串长度为0</li>
<li><code>-n string</code>: 字符串长度大于0</li>
<li><code>string1 == string2</code>: string1 等于 string2</li>
<li><code>string1 = string2</code>: string1 等于 string2</li>
<li><code>string1 &gt; string2</code>: 如果按照字典顺序string1排列在string2之后</li>
<li><code>string1 &lt; string2</code>: 如果按照字典顺序string1排列在string2之前</li>
</ul>
<h4 id="数字-整数-判断"><a href="#数字-整数-判断" class="headerlink" title="数字(整数)判断"></a>数字(整数)判断</h4><p>下面的表达式用于判断整数。</p>
<ul>
<li><code>[ integer1 -eq integer2 ]</code>：如果<code>integer1</code>等于<code>integer2</code>，则为<code>true</code>。</li>
<li><code>[ integer1 -ne integer2 ]</code>：如果<code>integer1</code>不等于<code>integer2</code>，则为<code>true</code>。</li>
<li><code>[ integer1 -le integer2 ]</code>：如果<code>integer1</code>小于或等于<code>integer2</code>，则为<code>true</code>。</li>
<li><code>[ integer1 -lt integer2 ]</code>：如果<code>integer1</code>小于<code>integer2</code>，则为<code>true</code>。</li>
<li><code>[ integer1 -ge integer2 ]</code>：如果<code>integer1</code>大于或等于<code>integer2</code>，则为<code>true</code>。</li>
<li><code>[ integer1 -gt integer2 ]</code>：如果<code>integer1</code>大于<code>integer2</code>，则为<code>true</code>。</li>
</ul>
<h4 id="文件判断"><a href="#文件判断" class="headerlink" title="文件判断"></a>文件判断</h4><p>以下表达式用来判断文件状态。仅列举常用判断，详细支持列表参考 <a href="https://tldp.org/LDP/abs/html/fto.html">https://tldp.org/LDP/abs/html/fto.html</a></p>
<ul>
<li><code>[ -a file ]</code>：如果 file 存在，则为<code>true</code>。</li>
<li><code>[ -d file ]</code>：如果 file 存在并且是一个目录，则为<code>true</code>。</li>
<li><code>[ -e file ]</code>：如果 file 存在，则为<code>true</code>, 同<code>-a</code>。</li>
<li><code>[ -f file ]</code>：如果 file 存在并且是一个普通文件，则为<code>true</code>。</li>
<li><code>[ -h file ]</code>：如果 file 存在并且是符号链接，则为<code>true</code>。</li>
<li><code>[ -L file ]</code>：如果 file 存在并且是符号链接，则为<code>true</code>, 同<code>-h</code>。</li>
<li><code>[ -p file ]</code>：如果 file 存在并且是一个命名管道，则为<code>true</code>。</li>
<li><code>[ -r file ]</code>：如果 file 存在并且可读（当前用户有可读权限），则为<code>true</code>。</li>
<li><code>[ -s file ]</code>：如果 file 存在且其长度大于零，则为<code>true</code>。</li>
<li><code>[ -w file ]</code>：如果 file 存在并且可写（当前用户拥有可写权限），则为<code>true</code>。</li>
<li><code>[ -x file ]</code>：如果 file 存在并且可执行（有效用户有执行／搜索权限），则为<code>true</code>。</li>
</ul>
<h3 id="switch-case"><a href="#switch-case" class="headerlink" title="switch case"></a>switch case</h3><p>bash也支持，switch case，语法如下。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">case EXPRESSION in</span><br><span class="line">  PATTERN_1)</span><br><span class="line">    STATEMENTS</span><br><span class="line">    ;;</span><br><span class="line">  PATTERN_2)</span><br><span class="line">    STATEMENTS</span><br><span class="line">    ;;</span><br><span class="line">  PATTERN_N)</span><br><span class="line">    STATEMENTS</span><br><span class="line">    ;;</span><br><span class="line">  *)</span><br><span class="line">    STATEMENTS</span><br><span class="line">    ;;</span><br><span class="line">esac</span><br></pre></td></tr></table></figure>

<p>例如</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">a=2</span><br><span class="line"><span class="keyword">case</span> <span class="variable">$a</span> <span class="keyword">in</span></span><br><span class="line">1)</span><br><span class="line">    <span class="built_in">echo</span> 11</span><br><span class="line">    ;;</span><br><span class="line">2)</span><br><span class="line">    <span class="built_in">echo</span> 22</span><br><span class="line">    ;;</span><br><span class="line">*)</span><br><span class="line">    ;;</span><br><span class="line"><span class="keyword">esac</span></span><br></pre></td></tr></table></figure>

<h3 id="循环"><a href="#循环" class="headerlink" title="循环"></a>循环</h3><h4 id="while-循环"><a href="#while-循环" class="headerlink" title="while 循环"></a>while 循环</h4><p><code>while</code>循环有一个判断条件，只要符合条件，就不断循环执行指定的语句。<br><code>condition</code>与if语句的相同，就不赘述了。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">while condition; do</span><br><span class="line">  command</span><br><span class="line">done</span><br></pre></td></tr></table></figure>

<h4 id="unitl-循环"><a href="#unitl-循环" class="headerlink" title="unitl 循环"></a>unitl 循环</h4><p><code>until</code>循环与<code>while</code>循环恰好相反，只要不符合判断条件（判断条件失败），就不断循环执行指定的语句。一旦符合判断条件，就退出循环。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">until condition; do</span><br><span class="line">  command</span><br><span class="line">done</span><br></pre></td></tr></table></figure>

<h4 id="for-in-循环"><a href="#for-in-循环" class="headerlink" title="for-in 循环"></a>for-in 循环</h4><p><code>for...in</code>循环用于遍历列表的每一项。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">for variable in list; do</span><br><span class="line">  commands</span><br><span class="line">done</span><br></pre></td></tr></table></figure>

<p>常见的几种用法</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">for i in 1 2 3; do</span><br><span class="line">    echo $i</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">for i in &#123;1..3&#125;; do</span><br><span class="line">    echo $i</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">list=(1 2 3)</span><br><span class="line">for i in $&#123;list[@]&#125;; do</span><br><span class="line">    echo $i</span><br><span class="line">done</span><br></pre></td></tr></table></figure>

<h4 id="for-循环"><a href="#for-循环" class="headerlink" title="for 循环"></a>for 循环</h4><p><code>for</code>循环还支持 C 语言的循环语法。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">for (( expression1; expression2; expression3 )); do</span><br><span class="line">  commands</span><br><span class="line">done</span><br></pre></td></tr></table></figure>

<p>上面代码中，<code>expression1</code>用来初始化循环条件，<code>expression2</code>用来决定循环结束的条件，<code>expression3</code>在每次循环迭代的末尾执行，用于更新值。</p>
<p>注意，循环条件放在双重圆括号之中。另外，圆括号之中使用变量，不必加上美元符号<code>$</code>。</p>
<p>例如</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">for ((i=1; i&lt;=3; i++)); do</span><br><span class="line">    echo $i</span><br><span class="line">done</span><br></pre></td></tr></table></figure>

<h4 id="跳出循环"><a href="#跳出循环" class="headerlink" title="跳出循环"></a>跳出循环</h4><p>Bash 提供了两个内部命令<code>break</code>和<code>continue</code>，用来在循环内部跳出循环。</p>
<p><code>break</code>命令立即终止循环，程序继续执行循环块之后的语句，即不再执行剩下的循环。<br><code>continue</code>命令立即终止本轮循环，开始执行下一轮循环。</p>
<h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><h3 id="函数定义"><a href="#函数定义" class="headerlink" title="函数定义"></a>函数定义</h3><p>Bash 函数定义的语法有两种，其中<code>fn</code>为定义的函数名称。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">第一种</span></span><br><span class="line">fn() &#123;</span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">codes</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">第二种</span></span><br><span class="line">function fn() &#123;</span><br><span class="line"><span class="meta prompt_">  # </span><span class="language-bash">codes</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="函数参数"><a href="#函数参数" class="headerlink" title="函数参数"></a>函数参数</h3><p>函数体内可以使用参数变量，获取函数参数。函数的参数变量，与脚本参数变量是一致的。</p>
<ul>
<li><code>${N}</code>：函数的第一个到第N个的参数。</li>
<li><code>$0</code>：函数所在的脚本名。</li>
<li><code>$#</code>：函数的参数总数。</li>
<li><code>$@</code>：函数的全部参数，参数之间使用空格分隔。</li>
<li><code>$*</code>：函数的全部参数，参数之间使用变量<code>$IFS</code>值的第一个字符分隔，默认为空格，但是可以自定义。</li>
</ul>
<h3 id="函数调用"><a href="#函数调用" class="headerlink" title="函数调用"></a>函数调用</h3><p><code>funcname arg1 arg ... argN</code> 的语法进行函数调用。主要函数的返回值和输出值（标准输出）的区别，这和主流编程语言不同</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">add() &#123;</span><br><span class="line">    declare -i res</span><br><span class="line">    res=0</span><br><span class="line">    for i in $@; do</span><br><span class="line">        res+=$i</span><br><span class="line">    done</span><br><span class="line">    echo $res</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">结果为10</span></span><br><span class="line">add 1 2 3 4</span><br></pre></td></tr></table></figure>

<h3 id="函数返回值"><a href="#函数返回值" class="headerlink" title="函数返回值"></a>函数返回值</h3><p><code>return</code>命令用于从函数返回一个值。返回值和命令的状态码一样，可以用<code>$?</code>拿到值。<br><code>return</code>也可以不接具体的值，则返回值是return命令的上一条命令的状态码。<br>如果不加<code>return</code>，则返回值是函数体最后一条命令的状态码。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">function func_return_value &#123;</span><br><span class="line">  return 10</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="关键概念"><a href="#关键概念" class="headerlink" title="关键概念"></a>关键概念</h2><h3 id="shebang"><a href="#shebang" class="headerlink" title="shebang"></a>shebang</h3><p>Shebang（也称为Hashbang）是一个由井号和叹号构成的字符序列<code>#!</code>， 其出现在可执行文本文件的第一行的前两个字符。<br>在文件中存在Shebang的情况下，类Unix操作系统的程序加载器会分析Shebang后的内容，将这些内容作为解释器指令，并调用该指令.</p>
<p>例如，shell脚本</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> Hello, world!</span><br></pre></td></tr></table></figure>

<p>python 脚本</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python -u</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Hello, world!&quot;</span>)</span><br></pre></td></tr></table></figure>

<h3 id="状态码"><a href="#状态码" class="headerlink" title="状态码"></a>状态码</h3><p>每个命令都会返回一个退出状态码（有时候也被称为返回状态）。</p>
<p>成功的命令返回 0，不成功的命令返回非零值，非零值通常都被解释成一个错误码。行为良好的 UNIX 命令、程序和工具都会返回 0 作为退出码来表示成功，虽然偶尔也会有例外。</p>
<p>状态码一般是程序的main函数的返回码，如c,c++。<br>如果是bash脚本，状态码的值则是 <code>exit</code> 命令的参数值。<br>当脚本以不带参数的 <code>exit</code> 命令来结束时，脚本的退出状态码就由脚本中最后执行的命令来决定，这与函数的 <code>return</code> 行为是一致的。</p>
<p>特殊变量<code>$?</code>可以查看上个命令的退出状态码</p>
<h3 id="文件描述符"><a href="#文件描述符" class="headerlink" title="文件描述符"></a>文件描述符</h3><p>文件描述符在形式上是一个非负整数。指向内核为每一个进程所维护的该进程打开文件的记录表。<br>当程序打开一个现有文件或者创建一个新文件时，内核向进程返回一个文件描述符。</p>
<h4 id="标准输入输出"><a href="#标准输入输出" class="headerlink" title="标准输入输出"></a>标准输入输出</h4><p>每个Unix进程（除了可能的守护进程）应均有三个标准的POSIX文件描述符，对应于三个标准流：</p>
<ul>
<li><code>0</code>：标准输入</li>
<li><code>1</code>：标准输出</li>
<li><code>2</code>：错误输出</li>
</ul>
<h4 id="打开新的文件描述符"><a href="#打开新的文件描述符" class="headerlink" title="打开新的文件描述符"></a>打开新的文件描述符</h4><p>手动指定描述符</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">exec 3&lt;&gt; /tmp/foo  #open fd 3.</span><br><span class="line">echo &quot;test&quot; &gt;&amp;3</span><br><span class="line">exec 3&gt;&amp;- #close fd 3.</span><br></pre></td></tr></table></figure>

<p>系统自动分配描述符，bash4.1开始支持(在macos报错，原因不明)</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line">FILENAME=abc.txt</span><br><span class="line"></span><br><span class="line">exec &#123;FD&#125;&lt;&gt;&quot;$FILENAME&quot;</span><br><span class="line">echo 11 &gt;&amp;FD</span><br><span class="line">echo 22 &gt;&amp;FD</span><br><span class="line"><span class="meta prompt_">$</span><span class="language-bash">FD&gt;&amp;-</span></span><br></pre></td></tr></table></figure>

<h4 id="描述符重定向"><a href="#描述符重定向" class="headerlink" title="描述符重定向"></a>描述符重定向</h4><ul>
<li><code>command &gt; file</code>: 将输出重定向到 file。</li>
<li><code>command &lt; file</code>: 将输入重定向到 file。</li>
<li><code>command &gt;&gt; file</code>: 将输出以追加的方式重定向到 file。</li>
<li><code>n &gt; file</code>: 将文件描述符为 n 的文件重定向到 file。</li>
<li><code>n &gt;&gt; file</code>: 将文件描述符为 n 的文件以追加的方式重定向到 file。</li>
<li><code>n &gt;&amp; m</code>: 将输出文件 m 和 n 合并。</li>
<li><code>n &lt;&amp; m</code>: 将输入文件 m 和 n 合并。</li>
</ul>
<p>所以命令中常见的<code>ls -al &gt; output.txt 2&gt;&amp;1</code>, 就是将标准输出和错误输出都重定向到一个文件。<br>等价于<code>ls -al &amp;&gt;output.txt</code>，本人偏好这种写法，比较简洁。</p>
<h3 id="IFS-Input-Field-Separators"><a href="#IFS-Input-Field-Separators" class="headerlink" title="IFS (Input Field Separators)"></a>IFS (Input Field Separators)</h3><p>IFS决定了bash在处理字符串的时候是如何进行单词切分。<br>IFS的默认值是空格，TAB，换行符，即<code> \t\n</code></p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$IFS</span>&quot;</span> | <span class="built_in">cat</span> -et</span></span><br><span class="line"> ^I$</span><br><span class="line"><span class="meta prompt_">$</span></span><br></pre></td></tr></table></figure>

<p>例如，在for循环的时候，如何区分每个item</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">for i in `echo -e &quot;foo bar\tfoobar\nfoofoo&quot;`; do</span><br><span class="line">    echo &quot;&#x27;$i&#x27; is the substring&quot;;</span><br><span class="line">done</span><br></pre></td></tr></table></figure>

<p>也可以自定义</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">OLD_IFS=&quot;$IFS&quot;</span><br><span class="line">IFS=&quot;:&quot;</span><br><span class="line">string=&quot;1:2:3&quot;</span><br><span class="line">for i in $string; do</span><br><span class="line">    echo &quot;&#x27;$i&#x27; is the substring&quot;;</span><br><span class="line">done</span><br><span class="line">IFS=$OLD_IFS</span><br></pre></td></tr></table></figure>

<h3 id="任务管理"><a href="#任务管理" class="headerlink" title="任务管理"></a>任务管理</h3><p>linux进程分前台（fg）和后台（bg）。</p>
<p>在命令的末尾添加<code>&amp;</code>可以将命令后台执行，一般配合输出重定向使用。</p>
<p><code>jobs</code>可以查看当前bash进程的子进程，并通过<code>fg</code>和<code>bg</code>进行前台和后台切换。</p>
<p><code>%1</code>代表后台的第一个进程，以此类推<code>%N</code>代表第n个.</p>
<p><code>control + Z</code>可以将当前前台程序暂停，配合<code>bg</code>可以将其转后台。</p>
<p><code>wait [pid]</code>可以等待子进程结束，如果不带pid参数则等待所有子进程结束。</p>
<figure class="highlight gams"><table><tr><td class="code"><pre><span class="line">bash<span class="number">-5.1</span><span class="symbol">$</span> <span class="built_in">sleep</span> <span class="number">100</span></span><br><span class="line">^Z</span><br><span class="line">[<span class="number">1</span>]+  已停止               <span class="built_in">sleep</span> <span class="number">100</span></span><br><span class="line">bash<span class="number">-5.1</span><span class="symbol">$</span> jobs</span><br><span class="line">[<span class="number">1</span>]+  已停止               <span class="built_in">sleep</span> <span class="number">100</span></span><br><span class="line">bash<span class="number">-5.1</span><span class="symbol">$</span> bg %<span class="number">1</span></span><br><span class="line">[<span class="number">1</span>]+ <span class="built_in">sleep</span> <span class="number">100</span> &amp;</span><br><span class="line">bash<span class="number">-5.1</span><span class="symbol">$</span> <span class="built_in">sleep</span> <span class="number">200</span> &amp;</span><br><span class="line">[<span class="number">2</span>] <span class="number">63603</span></span><br><span class="line">bash<span class="number">-5.1</span><span class="symbol">$</span> jobs</span><br><span class="line">[<span class="number">1</span>]-  运行中               <span class="built_in">sleep</span> <span class="number">100</span> &amp;</span><br><span class="line">[<span class="number">2</span>]+  运行中               <span class="built_in">sleep</span> <span class="number">200</span> &amp;</span><br><span class="line">bash<span class="number">-5.1</span><span class="symbol">$</span> wait %<span class="number">1</span></span><br><span class="line">[<span class="number">1</span>]-  已完成               <span class="built_in">sleep</span> <span class="number">100</span></span><br></pre></td></tr></table></figure>

<h4 id="后台进程并发控制"><a href="#后台进程并发控制" class="headerlink" title="后台进程并发控制"></a>后台进程并发控制</h4><p>可以利用jobs对后台进程并发数目进行控制</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">for i in &#123;1..30&#125;; do</span><br><span class="line">	sleep $((30+i)) &amp;</span><br><span class="line">	if [[ $(jobs | wc -l ) -gt 10 ]]; then</span><br><span class="line">		jobs</span><br><span class="line">		wait</span><br><span class="line">	fi</span><br><span class="line">done</span><br><span class="line">wait</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://wangdoc.com/bash/">https://wangdoc.com/bash/</a></li>
<li><a href="https://tldp.org/LDP/abs/html/fto.html">https://tldp.org/LDP/abs/html/fto.html</a></li>
<li><a href="https://zh.wikipedia.org/wiki/Shebang">https://zh.wikipedia.org/wiki/Shebang</a></li>
<li><a href="https://en.wikipedia.org/wiki/File_descriptor">https://en.wikipedia.org/wiki/File_descriptor</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Shell</tag>
      </tags>
  </entry>
  <entry>
    <title>Bitmap 原理及实现</title>
    <url>/2021/05/31/bitmap-python-edition/</url>
    <content><![CDATA[<p>所谓的 Bitmap 其实就是二进制位数组，由于元素是二进制位，每一个元素只占用1个bit，十分节省内存空间。</p>
<p>每一个bit有0、1两种状态，所以 Bitmap 适合应用于判断是否存在、桶排序（不含重复元素），具体来说可以用bitmap记录ip信息，实现布隆过滤器等等。</p>
<span id="more"></span>

<h2 id="Bitmap-原理"><a href="#Bitmap-原理" class="headerlink" title="Bitmap 原理"></a>Bitmap 原理</h2><p>Bitmap 可以看成是个二维数组，第一维取出的元素是byte，然后用第二维的index去访问该byte对应的位。</p>
<p>由于正常访问内存最小的单位的字节，操作具体的位需要位运算。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-05-31-16224684372402.jpg"></p>
<h3 id="位运算"><a href="#位运算" class="headerlink" title="位运算"></a>位运算</h3><p><strong>注意</strong>：这里的位移操作都是逻辑位移</p>
<p>一个byte有8bit， 设置某个位为1，需要用到<code>按位或 |</code></p>
<figure class="highlight maxima"><table><tr><td class="code"><pre><span class="line">第<span class="number">0</span>个bit： byte |= <span class="number">0b10000000</span></span><br><span class="line">第<span class="number">1</span>个bit： byte |= <span class="number">0b01000000</span></span><br><span class="line">...</span><br><span class="line">第<span class="number">7</span>个bit： byte |= <span class="number">0b00000001</span></span><br><span class="line"></span><br><span class="line">所以：设置byte的第n个bit(n取<span class="number">0</span>到<span class="number">7</span>)： byte |= (<span class="number">0b10000000</span> &gt;&gt; n)</span><br></pre></td></tr></table></figure>

<p>判断某个位是否为1，需要用到<code>按位与 &amp;</code><br>过程和上面类似, 满足<code>byte &amp; (0b10000000 &gt;&gt; n) == (0b10000000 &gt;&gt; n)</code>，则该位为1</p>
<p>将某个位置为0，需要用到<code>按位与 &amp;</code></p>
<figure class="highlight maxima"><table><tr><td class="code"><pre><span class="line">第<span class="number">0</span>个bit： byte &amp;= <span class="number">0b01111111</span></span><br><span class="line">第<span class="number">1</span>个bit： byte &amp;= <span class="number">0b10111111</span></span><br><span class="line">...</span><br><span class="line">第<span class="number">7</span>个bit： byte &amp;= <span class="number">0b01111110</span></span><br><span class="line"></span><br><span class="line">所以：第n个bit(n取<span class="number">0</span>到<span class="number">7</span>)： byte &amp;= ((<span class="number">0b10000000</span> &gt;&gt; n) ^ <span class="number">0b11111111</span>)</span><br><span class="line">Python中由于没有无符号数，所以算掩码（<span class="number">0b01111111</span>）时不能用按位取反。</span><br></pre></td></tr></table></figure>

<h3 id="Python实现"><a href="#Python实现" class="headerlink" title="Python实现"></a>Python实现</h3><p>将使用到的掩码设置为常量，性能会更好，这里的实现主要是为了体现思路，便于理解</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">BYTE_WIDTH = <span class="number">8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BitMap</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, size, fill=<span class="number">0</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>._array = <span class="built_in">bytearray</span>((fill <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(size//BYTE_WIDTH+<span class="number">1</span>)))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">set</span>(<span class="params">self, index</span>):</span><br><span class="line">        major, minor = <span class="built_in">divmod</span>(index, BYTE_WIDTH)</span><br><span class="line">        <span class="variable language_">self</span>._array[major] |= (<span class="number">0b10000000</span> &gt;&gt; minor)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get</span>(<span class="params">self, index</span>):</span><br><span class="line">        major, minor = <span class="built_in">divmod</span>(index, BYTE_WIDTH)</span><br><span class="line">        mask = <span class="number">0b10000000</span> &gt;&gt; minor</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">int</span>(<span class="variable language_">self</span>._array[major] &amp; mask == mask)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">clear</span>(<span class="params">self, index</span>):</span><br><span class="line">        major, minor = <span class="built_in">divmod</span>(index, BYTE_WIDTH)</span><br><span class="line">        <span class="variable language_">self</span>._array[major] &amp;= ((<span class="number">0b10000000</span> &gt;&gt; minor) ^ <span class="number">0b11111111</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_bin</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27; &#x27;</span>.join((<span class="string">&#x27;&#123;:0&gt;8&#125;&#x27;</span>.<span class="built_in">format</span>(<span class="built_in">bin</span>(i)[<span class="number">2</span>:]) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="variable language_">self</span>._array))</span><br></pre></td></tr></table></figure>

]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>数据结构</tag>
      </tags>
  </entry>
  <entry>
    <title>C扩展与gevent共存时的问题</title>
    <url>/2019/11/07/blocking-io-with-gevent-and-c-extension/</url>
    <content><![CDATA[<p>gevent是一个使用完全同步编程模型的可扩展的异步I&#x2F;O框架。</p>
<p>通用monkey.patch_all() 所有io操作函数, gevent可以以同步的方式编写异步代码. 在不更改代码的同时就可以使系统并发性能得到指数级提升。</p>
<p>这里有一个局限, c扩展中的io操作无法被patch, 会导致整个server阻塞</p>
<span id="more"></span>

<h2 id="一个简单的web-server"><a href="#一个简单的web-server" class="headerlink" title="一个简单的web server"></a>一个简单的web server</h2><p>这里使用flask写了web server 用于测试</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># filename: flask_app.py</span></span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, request</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/test&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test</span>():</span><br><span class="line">    time.sleep(<span class="number">0.2</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;hello&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    app.run()</span><br></pre></td></tr></table></figure>

<h3 id="不使用gevent"><a href="#不使用gevent" class="headerlink" title="不使用gevent"></a>不使用gevent</h3><p>安装依赖：<code>pip install flask gunicorn gevent</code></p>
<p>启动服务器：<code>gunicorn -w 1 --bind 127.0.0.1:5000 flask_app:app</code></p>
<p>测试性能：<code>siege -c 20 -r 1 &#39;http://127.0.0.1:5000/test&#39;</code></p>
<p><img data-src="https://image.ponder.work/mweb/2019-11-07-15731309158635.jpg"></p>
<p>由于只有一个worker进程，可以看到只有5qps，每个请求sleep 0.2秒，是符合预期的。</p>
<h3 id="使用gevent"><a href="#使用gevent" class="headerlink" title="使用gevent"></a>使用gevent</h3><p>启动服务器：<code>gunicorn -w 1 -k gevent --bind 127.0.0.1:5000 flask_app:app</code></p>
<p>测试性能：<code>siege -c 20 -r 1 &#39;http://127.0.0.1:5000/test&#39;</code></p>
<p><img data-src="https://image.ponder.work/mweb/2019-11-07-15731311981620.jpg" alt="-w348"></p>
<p>可以看到，性能有接近20倍的提升</p>
<h2 id="C扩展阻塞io与gevent协作的问题"><a href="#C扩展阻塞io与gevent协作的问题" class="headerlink" title="C扩展阻塞io与gevent协作的问题"></a>C扩展阻塞io与gevent协作的问题</h2><h3 id="加入阻塞io的C扩展"><a href="#加入阻塞io的C扩展" class="headerlink" title="加入阻塞io的C扩展"></a>加入阻塞io的C扩展</h3><p>这个程序通过sleep 2s 来模拟阻塞的io操作，所以每次调用会阻塞2s</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// sleep.c</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">  sleep(<span class="number">2</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后，自然是把这个C文件编成动态链接库：</p>
<p>Linux下的编译：</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">gcc -c -fPIC sleep.c</span><br><span class="line">gcc -shared sleep.o -o sleep.so</span><br></pre></td></tr></table></figure>

<p>然后在我们的web server 中调用这个动态库，使用ctypes调用</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">socket_block</span>():</span><br><span class="line">    <span class="keyword">from</span> ctypes <span class="keyword">import</span> cdll</span><br><span class="line">    lib = cdll.LoadLibrary(<span class="string">&#x27;./sleep.so&#x27;</span>)</span><br><span class="line">    lib.main()</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/test&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test</span>():</span><br><span class="line">    socket_block()</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;hello&#x27;</span></span><br></pre></td></tr></table></figure>

<p>测试后发现，gevent失效了，整个服务基本是串行阻塞状态<br><img data-src="https://image.ponder.work/mweb/2019-11-07-15731339898524.jpg" alt="-w454"></p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="尝试thread"><a href="#尝试thread" class="headerlink" title="尝试thread"></a>尝试thread</h4><p>使用线程, 基本没啥用, 不能解决问题</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/test&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test</span>():</span><br><span class="line">    t = threading.Thread(target=socket_block, args=())</span><br><span class="line">    t.setDaemon(<span class="number">1</span>)</span><br><span class="line">    t.start()</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;hello&#x27;</span></span><br></pre></td></tr></table></figure>

<p>使用 siege 进行测试, 看请求耗时, 所有请求基本是串行的, gevent没有效果</p>
<figure class="highlight nix"><table><tr><td class="code"><pre><span class="line">➜  <span class="symbol">~/projects</span> siege <span class="operator">-</span>c <span class="number">10</span> <span class="operator">-</span>r <span class="number">1</span> &#x27;http:<span class="operator">//</span><span class="number">127.0</span>.<span class="number">0.1</span>:<span class="number">5000</span><span class="operator">/</span>test&#x27;  <span class="operator">-</span>v</span><br><span class="line"><span class="operator">*</span><span class="operator">*</span> SIEGE <span class="number">4.0</span>.<span class="number">4</span></span><br><span class="line"><span class="operator">*</span><span class="operator">*</span> Preparing <span class="number">10</span> concurrent users for battle.</span><br><span class="line">The server is now under siege...</span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">16.02</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">16.02</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">16.02</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">16.02</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">16.02</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">16.03</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">16.02</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">16.02</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">20.03</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>    <span class="number">20.04</span> <span class="params">secs:</span>       <span class="number">9</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="operator">/</span>test</span><br></pre></td></tr></table></figure>

<h4 id="尝试multiprocess"><a href="#尝试multiprocess" class="headerlink" title="尝试multiprocess"></a>尝试multiprocess</h4><p>问题解决</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/test&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test</span>():</span><br><span class="line">    p = multiprocessing.Process(target=socket_block, args=())</span><br><span class="line">    p.start()</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;hello&#x27;</span></span><br></pre></td></tr></table></figure>

<p>使用 siege 进行测试, 请求没有被阻塞</p>
<figure class="highlight nix"><table><tr><td class="code"><pre><span class="line">➜  <span class="symbol">~/projects</span> siege <span class="operator">-</span>c <span class="number">5</span> <span class="operator">-</span>r <span class="number">1</span> &#x27;http:<span class="operator">//</span><span class="number">127.0</span>.<span class="number">0.1</span>:<span class="number">5000</span><span class="operator">/</span>test&#x27;  <span class="operator">-</span>v</span><br><span class="line"><span class="operator">*</span><span class="operator">*</span> SIEGE <span class="number">4.0</span>.<span class="number">4</span></span><br><span class="line"><span class="operator">*</span><span class="operator">*</span> Preparing <span class="number">5</span> concurrent users for battle.</span><br><span class="line">The server is now under siege...</span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>     <span class="number">0.01</span> <span class="params">secs:</span>       <span class="number">5</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>     <span class="number">0.02</span> <span class="params">secs:</span>       <span class="number">5</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>     <span class="number">0.03</span> <span class="params">secs:</span>       <span class="number">5</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>     <span class="number">0.03</span> <span class="params">secs:</span>       <span class="number">5</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="symbol">/test</span></span><br><span class="line">HTTP<span class="symbol">/1.1</span> <span class="number">200</span>     <span class="number">0.04</span> <span class="params">secs:</span>       <span class="number">5</span> bytes <span class="operator">==</span><span class="operator">&gt;</span> GET  <span class="operator">/</span>test</span><br></pre></td></tr></table></figure>

<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>webserver主体进程使用gevent, 将阻塞的c扩展网络io操作放到另一个进程中执行, 可以改造成一个服务</p>
<p>具体可以用这下面两种实现</p>
<ul>
<li>celery</li>
<li>multiprocess</li>
</ul>
<p>还有一种对动态库进行patch的方案（<a href="https://github.com/douban/greenify">greenify</a>），只在linux下有效，就没怎么研究了</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Gevent</tag>
      </tags>
  </entry>
  <entry>
    <title>Python2 中 旧式类与property引发的bug</title>
    <url>/2019/12/01/bug-in-python-old-style-class-and-property/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>话接上回，说是接手了一个82年的<del>拉菲</del>Python项目，这次又发现了一个新坑</p>
<p>项目中用了一个上下文类，用于存储本次请求的一些数据，在开发过程中我想把这个上下文类dump成json，详细分析里面的数据，然而发现上下文类的行为不符合预期</p>
<span id="more"></span>

<h2 id="症状"><a href="#症状" class="headerlink" title="症状"></a>症状</h2><p>上下文类大概是这样</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Context</span>():</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>._input_json = <span class="literal">None</span></span><br><span class="line">        <span class="variable language_">self</span>._result_dict = <span class="built_in">dict</span>()</span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">input_json</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;get _input_json&#x27;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>._input_json</span><br><span class="line"></span><br><span class="line"><span class="meta">    @input_json.setter</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">input_json</span>(<span class="params">self, input_json</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;set _input_json %s &#x27;</span> % input_json</span><br><span class="line">        <span class="variable language_">self</span>._input_json = input_json</span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">result_dict</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;get _result_dict&#x27;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>._result_dict</span><br><span class="line"></span><br><span class="line"><span class="meta">    @result_dict.setter</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">result_dict</span>(<span class="params">self, result_dict</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;set _result_dict %s &#x27;</span> % result_dict</span><br><span class="line">        <span class="variable language_">self</span>._result_dict = result_dict</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 我附加的to_dict方法</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">to_dict</span>(<span class="params">self</span>):</span><br><span class="line">        tmp = &#123;&#125;</span><br><span class="line">        <span class="keyword">for</span> k, v <span class="keyword">in</span> <span class="variable language_">self</span>.__dict__.items():</span><br><span class="line">            <span class="keyword">if</span> k.startswith(<span class="string">&#x27;_&#x27;</span>):</span><br><span class="line">                tmp[k] = v</span><br><span class="line">        <span class="keyword">return</span> tmp</span><br></pre></td></tr></table></figure>

<p>测试之后发现，结果明显不符合预期，两个属性只输出了一个</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> test_property <span class="keyword">import</span>  Context</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>c = Context()</span><br><span class="line"><span class="meta">... </span>c.input_json = &#123;<span class="string">&#x27;a&#x27;</span>: <span class="number">1</span>&#125;</span><br><span class="line"><span class="meta">... </span>c.result_dict[<span class="string">&#x27;b&#x27;</span>] = <span class="number">2</span></span><br><span class="line">...</span><br><span class="line">get _result_dict</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>c.to_dict()</span><br><span class="line">&#123;<span class="string">&#x27;_input_json&#x27;</span>: <span class="literal">None</span>, <span class="string">&#x27;_result_dict&#x27;</span>: &#123;<span class="string">&#x27;b&#x27;</span>: <span class="number">2</span>&#125;&#125;</span><br></pre></td></tr></table></figure>

<h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>看测试代码我们可以发现，在访问<code>result_dict</code>属性的时候，property是工作正常的（有对应的print)</p>
<p>但是对应设置<code>input_json</code>的时候, 却没有看到对应的print输出</p>
<p>所以可以断定，此处的property工作不正常。</p>
<p>仔细看代码后，我发现<code>Context</code>是旧式类。可以看到，<code>A</code>, <code>B</code>, <code>C</code>三中类的写法，其中<code>A</code>和<code>B</code>都是旧式类<code>&lt;type &#39;classobj&#39;&gt;</code>, <code>C</code>是新式类。（旧式类只在Python2中存在）。<br>我们这里<code>Context</code>的写法和<code>B</code>是一样的。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">A</span>:</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">pass</span></span><br><span class="line">...</span><br><span class="line"><span class="meta">... </span><span class="keyword">class</span> <span class="title class_">B</span>():</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">pass</span></span><br><span class="line">...</span><br><span class="line"><span class="meta">... </span><span class="keyword">class</span> <span class="title class_">C</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">pass</span></span><br><span class="line">...</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(A)</span><br><span class="line">&lt;<span class="built_in">type</span> <span class="string">&#x27;classobj&#x27;</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(B)</span><br><span class="line">&lt;<span class="built_in">type</span> <span class="string">&#x27;classobj&#x27;</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(C)</span><br><span class="line">&lt;<span class="built_in">type</span> <span class="string">&#x27;type&#x27;</span>&gt;</span><br></pre></td></tr></table></figure>

<p>然后自然就怀疑旧式类对property装饰器的支持存在问题。<br>一通google之后，确定旧式类是不支持property。</p>
<p><img data-src="https://image.ponder.work/mweb/2019-12-02-15752721846597.jpg"></p>
<p>确切地说，是对property的支持不完整，具体来说有以下3点。</p>
<ul>
<li>支持property的getter</li>
<li>不支持property的setter</li>
<li>不支持property的赋值保护</li>
</ul>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://help.semmle.com/wiki/display/PYTHON/Property+in+old-style+class">https://help.semmle.com/wiki/display/PYTHON/Property+in+old-style+class</a></li>
<li><a href="https://stackoverflow.com/questions/9962037/pythons-property-decorator-does-not-work-as-expected">https://stackoverflow.com/questions/9962037/pythons-property-decorator-does-not-work-as-expected</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Property</tag>
      </tags>
  </entry>
  <entry>
    <title>用exxo将Python程序编译成单一可执行文件</title>
    <url>/2020/02/29/build-python-program-to-standalone-binary/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>golang将程序编译成一个可执行文件，部署起来特别方便。</p>
<p>那么Python是否也有类似解决方案呢？单一可执行文件，免去安装Python环境的麻烦，也避免了直接暴露源码程序。</p>
<p>经过多次搜索之后找到解决方案 <a href="https://github.com/mbachry/exxo">exxo</a></p>
<p><em>注意：exxo只支持linux64平台</em></p>
<span id="more"></span>

<h2 id="指路"><a href="#指路" class="headerlink" title="指路"></a>指路</h2><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>首先下载安装exxo</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">wget https://bintray.com/artifact/download/mbachry/exxo/exxo-0.0.7.tar.xz  <span class="comment"># 下载 </span></span><br><span class="line"></span><br><span class="line">tar xf exxo-0.0.7.tar.xz  <span class="comment"># 解压</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">mv</span> exxo /usr/local/bin   <span class="comment"># 移动到可执行文件目录（可选）</span></span><br></pre></td></tr></table></figure>

<h3 id="创建环境"><a href="#创建环境" class="headerlink" title="创建环境"></a>创建环境</h3><p>通过 exxo 创建一个python虚拟环境，用于编译我们的程序</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">exxo venv /tmp/myenv    <span class="comment"># 创建环境</span></span><br><span class="line"><span class="built_in">source</span> /tmp/myenv/bin/activate   <span class="comment"># 激活环境</span></span><br></pre></td></tr></table></figure>

<h3 id="编译程序"><a href="#编译程序" class="headerlink" title="编译程序"></a>编译程序</h3><p>先写一个简单的程序: aa.py</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    os.system(<span class="string">&#x27;ls -al&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure>

<p>再根据这个程序编写setup.py, 这一步是关键，不熟悉的同学可以去学习一下setuptools的语法。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">#condig=utf8</span></span><br><span class="line"><span class="keyword">from</span> setuptools <span class="keyword">import</span> setup, find_packages</span><br><span class="line"></span><br><span class="line">requirements = [</span><br><span class="line">    <span class="comment"># 这里可以写上需要的依赖</span></span><br><span class="line">    <span class="comment"># &#x27;setuptools&#x27;,</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">setup(</span><br><span class="line">    name=<span class="string">&#x27;pyaa&#x27;</span>,   <span class="comment"># 编译出的文件的名称</span></span><br><span class="line">    version=<span class="string">&#x27;0.0.1&#x27;</span>,</span><br><span class="line">    packages=find_packages(),</span><br><span class="line">    include_package_data=<span class="literal">True</span>,</span><br><span class="line">    install_requires=requirements,</span><br><span class="line">    zip_safe=<span class="literal">True</span>,</span><br><span class="line">    py_modules=[</span><br><span class="line">        <span class="string">&quot;aa&quot;</span>,</span><br><span class="line">    ],</span><br><span class="line">    entry_points=&#123;  <span class="comment"># python 生成可执行文件的入口</span></span><br><span class="line">        <span class="string">&#x27;console_scripts&#x27;</span>: [</span><br><span class="line">            <span class="string">&#x27;pyaa = aa:main&#x27;</span>,</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<p>执行<code>exxo build</code>编译，生成文件<code>dist/pyaa</code></p>
<p>最后，测试该文件，功能正常。</p>
<figure class="highlight tap"><table><tr><td class="code"><pre><span class="line">$ ./dist/pyaa</span><br><span class="line">总用量 19348</span><br><span class="line">drwxrwxr-x<span class="number"> 4 </span>ruan ruan    <span class="number"> 4096 </span>3月  <span class="number"> 1 </span>11:25 .</span><br><span class="line">drwxrwxr-x<span class="number"> 6 </span>ruan ruan    <span class="number"> 4096 </span>2月 <span class="number"> 29 </span>11:48 ..</span><br><span class="line">-rw-rw-r--<span class="number"> 1 </span>ruan ruan      <span class="number"> 87 </span>3月  <span class="number"> 1 </span>11:14 aa.py</span><br><span class="line">drwxrwxr-x<span class="number"> 2 </span>ruan ruan    <span class="number"> 4096 </span>3月  <span class="number"> 1 </span>11:26 dist</span><br><span class="line">-rw-r--r--<span class="number"> 1 </span>ruan ruan<span class="number"> 19784872 </span>8月 <span class="number"> 29 </span>2016 exxo-0.0.7.tar.xz</span><br><span class="line">drwxrwxr-x<span class="number"> 2 </span>ruan ruan    <span class="number"> 4096 </span>3月  <span class="number"> 1 </span>11:24 __pycache__</span><br><span class="line">-rw-rw-r--<span class="number"> 1 </span>ruan ruan     <span class="number"> 505 </span>3月  <span class="number"> 1 </span>11:25 setup.py</span><br></pre></td></tr></table></figure>

]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>centos开关机日志分析</title>
    <url>/2016/06/22/centos%E5%BC%80%E5%85%B3%E6%9C%BA%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>在工作中发现有获取linux具体开关机时间和类型的需求，可以通过分析<code>/var/log/wtmp</code>日志文件得到。<br>通过<code>last -x -F</code>可以将<code>/var/log/wtmp</code>输出以下格式。</p>
<span id="more"></span>
<figure class="highlight tap"><table><tr><td class="code"><pre><span class="line">root     pts/1        172.16.3.245     Sun Jun<span class="number"> 12 </span>10:14:47<span class="number"> 2016 </span>- Tue Jun<span class="number"> 14 </span>08:34:31<span class="number"> 2016 </span>(1+22:19)</span><br><span class="line">root     pts/0        172.16.3.245     Sun Jun<span class="number"> 12 </span>10:14:37<span class="number"> 2016 </span>- Tue Jun<span class="number"> 14 </span>08:34:25<span class="number"> 2016 </span>(1+22:19)</span><br><span class="line">root     ttyS0                         Sun Jun<span class="number"> 12 </span>10:14:24<span class="number"> 2016 </span>- down                     (5+03:49)</span><br><span class="line">runlevel (to lvl 3)   3.10.0           Sun Jun<span class="number"> 12 </span>10:14:17<span class="number"> 2016 </span>- Fri Jun<span class="number"> 17 </span>14:03:40<span class="number"> 2016 </span>(5+03:49)</span><br><span class="line">root     pts/1        172.16.3.145     Sun Jun<span class="number"> 12 </span>10:13:57<span class="number"> 2016 </span>- Sun Jun<span class="number"> 12 </span>10:13:57<span class="number"> 2016 </span> (00:00)</span><br><span class="line">reboot   system boot  3.10.0           Sun Jun<span class="number"> 12 </span>10:13:18<span class="number"> 2016 </span>- Fri Jun<span class="number"> 17 </span>14:03:40<span class="number"> 2016 </span>(5+03:50)</span><br><span class="line">shutdown system down  3.10.0           Sun Jun<span class="number"> 12 </span>10:12:51<span class="number"> 2016 </span>- Sun Jun<span class="number"> 12 </span>10:13:18<span class="number"> 2016 </span> (00:00)</span><br><span class="line">root     pts/1        172.16.3.145     Sun Jun<span class="number"> 12 </span>10:12:47<span class="number"> 2016 </span>- down                      (00:00)</span><br><span class="line">root     pts/20       172.16.3.211     Sun Jun<span class="number"> 12 </span>10:07:58<span class="number"> 2016 </span>- down                      (00:04)</span><br><span class="line">root     pts/19       172.16.3.119     Sun Jun<span class="number"> 12 </span>10:07:50<span class="number"> 2016 </span>- down                      (00:05)</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">root     pts/3        172.16.3.145     Sun Jun<span class="number"> 12 </span>09:10:15<span class="number"> 2016 </span>- down                      (01:02)</span><br><span class="line">root     pts/2        172.16.3.145     Sun Jun<span class="number"> 12 </span>09:10:15<span class="number"> 2016 </span>- down                      (01:02)</span><br><span class="line">root     tty1                          Sun Jun<span class="number"> 12 </span>09:09:37<span class="number"> 2016 </span>- down                      (01:03)</span><br><span class="line">runlevel (to lvl 3)   3.10.0           Sun Jun<span class="number"> 12 </span>09:09:22<span class="number"> 2016 </span>- Sun Jun<span class="number"> 12 </span>10:12:51<span class="number"> 2016 </span> (01:03)</span><br><span class="line">root     pts/1        172.16.3.245     Sun Jun<span class="number"> 12 </span>09:09:10<span class="number"> 2016 </span>- Sun Jun<span class="number"> 12 </span>10:12:47<span class="number"> 2016 </span> (01:03)</span><br><span class="line">reboot   system boot  3.10.0           Sun Jun<span class="number"> 12 </span>09:07:24<span class="number"> 2016 </span>- Sun Jun<span class="number"> 12 </span>10:12:51<span class="number"> 2016 </span> (01:05)</span><br><span class="line">root     pts/14       172.16.3.211     Sun Jun<span class="number"> 12 </span>08:57:19<span class="number"> 2016 </span>- crash                     (00:10)</span><br><span class="line">root     pts/7        172.16.3.245     Sun Jun<span class="number"> 12 </span>08:52:13<span class="number"> 2016 </span>- crash                     (00:15)</span><br><span class="line">root     pts/11       172.16.3.245     Sun Jun<span class="number"> 12 </span>08:51:38<span class="number"> 2016 </span>- crash                     (00:15)</span><br><span class="line">root     pts/10       172.16.3.211     Sun Jun<span class="number"> 12 </span>08:46:06<span class="number"> 2016 </span>- crash                     (00:21)</span><br><span class="line">root     pts/9        172.16.3.30      Sun Jun<span class="number"> 12 </span>08:41:33<span class="number"> 2016 </span>- crash                     (00:25)</span><br></pre></td></tr></table></figure>

<h2 id="性空"><a href="#性空" class="headerlink" title="性空"></a>性空</h2><h3 id="具体一条记录"><a href="#具体一条记录" class="headerlink" title="具体一条记录"></a>具体一条记录</h3><table>
<thead>
<tr>
<th>用户</th>
<th>终端类型</th>
<th>登录ip</th>
<th>开始时间</th>
<th>结束时间</th>
<th>持续时长</th>
</tr>
</thead>
<tbody><tr>
<td>root</td>
<td>pts&#x2F;5</td>
<td>172.16.3.129</td>
<td>Fri Jun 17 14:03:50 2016</td>
<td>Fri Jun 17 14:03:42 2016</td>
<td>(00:00)</td>
</tr>
</tbody></table>
<p>每一行为一条会话记录，有以下6个部分。</p>
<ol>
<li>用户名<ul>
<li>linux用户名</li>
<li>reboot 系统关机或重启</li>
<li>shutdown 关机</li>
</ul>
</li>
<li>终端类型<ul>
<li>tty 本机终端</li>
<li>pst 远程登录终端</li>
</ul>
</li>
<li>登录ip</li>
<li>开始时间（可靠）</li>
<li>结束时间（不可靠）<ul>
<li>具体时间：结束时间，若不正常关机，此时间就不准确</li>
<li>down：系统关机退出</li>
<li>crash：系统不正常关机退出</li>
</ul>
</li>
<li>持续时长<ul>
<li>(5+03:49) 日，小时，分</li>
</ul>
</li>
</ol>
<h3 id="分析开关机时间思路"><a href="#分析开关机时间思路" class="headerlink" title="分析开关机时间思路"></a>分析开关机时间思路</h3><ol>
<li>首先找到开始为<code>reboot</code>的行，获取此行的“开始时间”得到这一次的开机时间。</li>
<li>如果<code>reboot</code>行的下一行为<code>shutdown</code>，则为正常关机，获取此行的“开始时间”得到关机时间。若持续时间为<code>(00:00)</code>则为重启。</li>
<li>如果<code>reboot</code>行的下一行不为<code>shutdown</code>，则为不正常关机，下面的登录会话的“结束时间”必然伴随着<code>crash</code>。找到最近的一条<code>crash</code>记录，拿到这条记录的“开始时间”和“持续时长”，相加即是关机时间。</li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Linux</tag>
      </tags>
  </entry>
  <entry>
    <title>关闭子进程打开的文件描述符</title>
    <url>/2022/08/30/close-subprocess-opened-fd/</url>
    <content><![CDATA[<p>我们在测试代码时，由于需要经常重启服务，经常会发现服务端口被占用。<br>一般kill掉后台进程就ok了，但是如果服务有启动一些常驻的后台程序，可能也会导致端口不能释放。</p>
<p>在类UNIX系统中，一切被打开的文件、端口被抽象为文件描述符（file descriptor）<br>从python3.4开始，文件描述符默认是non-inheritable，也就是子进程不会共享文件描述符。</p>
<span id="more"></span>

<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>一般为了实现多进程、多线程的webserver，服务端口fd必须设置为继承（set_inheritable），这样才能多进程监听一个端口（配合SO_REUSEPORT）<br>典型的是使用flask的测试服务器的场景，这里我们写一段代码模拟。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> socket, os</span><br><span class="line">server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line">server.bind((<span class="string">&#x27;127.0.0.1&#x27;</span>, <span class="number">22222</span>))</span><br><span class="line">server.set_inheritable(<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">os.system(<span class="string">&quot;python -c &#x27;import time;time.sleep(1000)&#x27; &quot;</span>)</span><br></pre></td></tr></table></figure>

<p>我们通过<code>lsof -p {pid}</code>可以看到这两个进程的所有文件描述符<br>server进程, 可以看到服务端口的fd是4</p>
<figure class="highlight tap"><table><tr><td class="code"><pre><span class="line">COMMAND   PID  FD      TYPE             DEVICE  SIZE/OFF       NODE NAME</span><br><span class="line">ptpython<span class="number"> 6214 </span>cwd       DIR              253,0     <span class="number"> 4096 </span><span class="number"> 872946898 </span>/</span><br><span class="line">...</span><br><span class="line">ptpython<span class="number"> 6214 </span>  0u      CHR             136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">ptpython<span class="number"> 6214 </span>  1u      CHR             136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">ptpython<span class="number"> 6214 </span>  2u      CHR             136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">ptpython<span class="number"> 6214 </span>  3r      CHR                1,9       0t0      <span class="number"> 2057 </span>/dev/urandom</span><br><span class="line">ptpython<span class="number"> 6214 </span>  4u     sock                0,7       0t0  <span class="number"> 58345077 </span>protocol: TCP</span><br><span class="line">ptpython<span class="number"> 6214 </span>  5u  a_inode               0,10        <span class="number"> 0 </span>     <span class="number"> 8627 </span>[eventpoll]</span><br><span class="line">ptpython<span class="number"> 6214 </span>  6u     unix 0x0000000000000000       0t0  <span class="number"> 58368029 </span>socket</span><br><span class="line">ptpython<span class="number"> 6214 </span>  7u     unix 0x0000000000000000       0t0  <span class="number"> 58368030 </span>socket</span><br></pre></td></tr></table></figure>

<p>sleep子进程，也拥有fd&#x3D;4的文件描述符</p>
<figure class="highlight tap"><table><tr><td class="code"><pre><span class="line">COMMAND   PID  FD   TYPE DEVICE  SIZE/OFF       NODE NAME</span><br><span class="line">python <span class="number"> 18022 </span>cwd    DIR  253,0     <span class="number"> 4096 </span><span class="number"> 872946898 </span>/</span><br><span class="line">...</span><br><span class="line">python <span class="number"> 18022 </span>  0u   CHR 136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">python <span class="number"> 18022 </span>  1u   CHR 136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">python <span class="number"> 18022 </span>  2u   CHR 136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">python <span class="number"> 18022 </span>  4u  sock    0,7       0t0  <span class="number"> 58345077 </span>protocol: TCP</span><br></pre></td></tr></table></figure>

<p>如果server进程退出时，sleep进程没有退出，fd&#x3D;4对应的端口就被占用了，服务也就不能正常启动了。</p>
<h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><h3 id="手动清理"><a href="#手动清理" class="headerlink" title="手动清理"></a>手动清理</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">os.system(<span class="string">f&#x27;lsof -p <span class="subst">&#123;os.getpid()&#125;</span>&#x27;</span>)</span><br><span class="line">os.closerange(<span class="number">3</span>, <span class="number">100</span>)  <span class="comment"># 这里假定打开文件描述符不会超过100</span></span><br><span class="line">time.sleep(<span class="number">5</span>)</span><br><span class="line">os.system(<span class="string">f&#x27;lsof -p <span class="subst">&#123;os.getpid()&#125;</span>&#x27;</span>)</span><br><span class="line"><span class="comment"># 后面执行需要的业务代码</span></span><br></pre></td></tr></table></figure>

<h3 id="使用close-fds"><a href="#使用close-fds" class="headerlink" title="使用close_fds"></a>使用close_fds</h3><p>使用subprocess库而不是os来启动子程序， 通过close_fds参数关闭多余的文件描述符</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> subprocess</span><br><span class="line">subprocess.call(<span class="string">&quot;python -c &#x27;import time;time.sleep(1000)&#x27;&quot;</span>, shell=<span class="literal">True</span>, close_fds=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://docs.python.org/3/library/os.html#inheritance-of-file-descriptors">https://docs.python.org/3/library/os.html#inheritance-of-file-descriptors</a></li>
<li><a href="https://docs.python.org/3/library/subprocess.html#subprocess.Popen">https://docs.python.org/3/library/subprocess.html#subprocess.Popen</a></li>
<li><a href="https://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python#answer-25069136">https://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python#answer-25069136</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>一致性哈希原理及实现</title>
    <url>/2021/06/02/consistent-hashing/</url>
    <content><![CDATA[<p>一致性哈希是一种特殊的哈希算法。在使用一致哈希算法后，哈希表槽位(slots)数的改变平均只需要对 K&#x2F;n 个key需要重新映射，其中K是key的数量，n是槽位数量。然而在传统的哈希表中，添加或删除一个槽位的几乎需要对所有关键字进行重新映射。</p>
<p>谈到一致性哈希（Consistent hashing），就得先讲一下分布式存储。<br>比如我们有2000w条数据，一台机器存不下，那么我们可以把分成10份每份200w条存到10台机器上。<br>这样存储就不成问题，但是查询效率很低，查一条数据要每台机器都查一遍。<br>如果这些数据能够分类，每一类存到一台机器上，查询前先知道数据的类别，就可以直接定位到某台机器，效率就高了。</p>
<span id="more"></span>
<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>那么就得找到一个通用而且均匀的分类方法，可以想到先哈希再取模<code>hash(data) % N</code></p>
<p>现有这几个数据apple, banana, cherry，durian，希望存储到有3台机器的服务.</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 假设自定义了hash函数，有以下返回</span></span><br><span class="line"><span class="attribute">hash</span>(<span class="string">&quot;apple&quot;</span>) % <span class="number">3</span> == <span class="number">30</span> % <span class="number">3</span> == <span class="number">0</span></span><br><span class="line"><span class="attribute">hash</span>(<span class="string">&quot;banada&quot;</span>) % <span class="number">3</span> == <span class="number">31</span> % <span class="number">3</span> == <span class="number">1</span></span><br><span class="line"><span class="attribute">hash</span>(<span class="string">&quot;cherry&quot;</span>) % <span class="number">3</span> == <span class="number">32</span> % <span class="number">3</span> == <span class="number">2</span></span><br></pre></td></tr></table></figure>
<p><img data-src="https://image.ponder.work/mweb/2021-06-03-16226066038043.jpg"></p>
<p>如果B机器宕机了，需要将取模余数和机器重新映射，这时发现3&#x2F;4的数据都需要迁移<br><img data-src="https://image.ponder.work/mweb/2021-06-03-16226068389571.jpg"></p>
<p>其实当B机器宕机时，取模的除数可以不改成2，依然是<code>hash(data) % 3</code>，这样余数就不会变，只需要把余数和机器的映射改一下，将原先B机器的映射到A机器上，这样只需要迁移1&#x2F;4的数据.<br><img data-src="https://image.ponder.work/mweb/2021-06-03-16226412563166.jpg"></p>
<p>但是现在取模的除数和机器数目相等，只能应对机器减少的情况，增加机器就没法处理了。<br>这时可以<strong>用一个比较大的数作为除数</strong>（比如3000），把除数在一定范围内的都映射到某台机器，增加机器只需要调整余数和机器的映射就行了。<br><img data-src="https://image.ponder.work/mweb/2021-06-03-16226416942953.jpg"></p>
<p>到这里一致性哈希的基本原理已经介绍完了，但对于新增服务器的情况还存在一些问题。<br>新增的服务器D只分担了C服务器的负载，服务器 A 和 B 并没有因为 D 服务器的加入而减少负载压力。<br>针对这个问题，可以把D当做多台机器，均匀地放置，这样所有机器的负载都得到分担，也就是所谓的引入虚拟节点。<br><img data-src="https://image.ponder.work/mweb/2021-06-03-16226421634795.jpg"></p>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p>下面用Python来实现一致性哈希，这里实现不带虚拟节点的版本。</p>
<p>代码实现和上文分析，有几点细节有些不同</p>
<ul>
<li>简化实现，使用<code>hashlib.sha1</code>作为哈希函数</li>
<li>取模的除数设置为<code>2^32 - 1</code>，这是C语言中<code>unsiged int</code>的最大取值。</li>
<li>根据我们的除数，余数的取值空间就是[0, 2^32 - 1], 可以看成一个首位相连的环。</li>
<li>余数和机器的映射不需要单独维护，只要将机器也哈希和取模，就得到机器在环上的位置。</li>
<li>机器分布在环上，将余数的取值范围分割成多个区间，每个区间对应一台机器，每台机器负责上一个机器位置到当前机器位置的数据。<br><img data-src="https://image.ponder.work/mweb/2021-06-03-16226908837252.jpg"></li>
</ul>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> hashlib</span><br><span class="line"></span><br><span class="line">MASK = <span class="number">2</span> ** <span class="number">32</span> - <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">myhash</span>(<span class="params">string</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">int</span>(hashlib.sha1(string.encode(<span class="string">&quot;utf-8&quot;</span>)).hexdigest(), <span class="number">16</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_pos</span>(<span class="params">obj</span>):</span><br><span class="line">    <span class="keyword">return</span> myhash(obj) % MASK</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Server</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, addr</span>):</span><br><span class="line">        <span class="variable language_">self</span>.addr = addr</span><br><span class="line">        <span class="variable language_">self</span>.pos = get_pos(<span class="variable language_">self</span>.addr)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__hash__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> myhash(<span class="variable language_">self</span>.addr)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;&lt;Server &#123;&#125;; &#123;:,&#125;&gt;&#x27;</span>.<span class="built_in">format</span>(<span class="variable language_">self</span>.addr, <span class="variable language_">self</span>.pos)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConsistentHashing</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.servers = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add_server</span>(<span class="params">self, server</span>):</span><br><span class="line">        <span class="keyword">assert</span> server <span class="keyword">not</span> <span class="keyword">in</span> <span class="variable language_">self</span>.servers</span><br><span class="line">        <span class="variable language_">self</span>.servers.append(server)</span><br><span class="line">        <span class="variable language_">self</span>.servers.sort(key=<span class="keyword">lambda</span> i: i.pos)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_server</span>(<span class="params">self, key</span>):</span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">len</span>(<span class="variable language_">self</span>.servers) &gt; <span class="number">0</span></span><br><span class="line">        pos = get_pos(key)</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="variable language_">self</span>.servers:</span><br><span class="line">            <span class="keyword">if</span> i.pos &gt;= pos:</span><br><span class="line">                <span class="keyword">return</span> i</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.servers[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    ch = ConsistentHashing()</span><br><span class="line">    ch.add_server(Server(<span class="string">&#x27;192.168.1.1:80&#x27;</span>))</span><br><span class="line">    ch.add_server(Server(<span class="string">&#x27;192.168.1.2:80&#x27;</span>))</span><br><span class="line">    ch.add_server(Server(<span class="string">&#x27;192.168.1.3:80&#x27;</span>))</span><br><span class="line">    <span class="built_in">print</span>(ch.servers)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;&#123;:,&#125;&#x27;</span>.<span class="built_in">format</span>(get_pos(<span class="string">&#x27;123&#x27;</span>)), ch.get_server(<span class="string">&#x27;123&#x27;</span>))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;&#123;:,&#125;&#x27;</span>.<span class="built_in">format</span>(get_pos(<span class="string">&#x27;xxx&#x27;</span>)), ch.get_server(<span class="string">&#x27;xxx&#x27;</span>))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;&#123;:,&#125;&#x27;</span>.<span class="built_in">format</span>(get_pos(<span class="string">&#x27;1&#x27;</span>)), ch.get_server(<span class="string">&#x27;1&#x27;</span>))</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://segmentfault.com/a/1190000021199728">https://segmentfault.com/a/1190000021199728</a></li>
<li><a href="https://www.jianshu.com/p/528ce5cd7e8f">https://www.jianshu.com/p/528ce5cd7e8f</a></li>
<li><a href="https://en.wikipedia.org/wiki/Consistent_hashing">https://en.wikipedia.org/wiki/Consistent_hashing</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>分布式</tag>
        <tag>算法</tag>
      </tags>
  </entry>
  <entry>
    <title>在Java环境中使用PMML执行xgboost模型</title>
    <url>/2020/04/10/convent-xgboost-model-to-pmml-guide/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>最近有个需求, 在Java环境环境运行xgboost模型, 查询资料后发现PMML可以解决这个问题.</p>
<p>PMML是数据挖掘的一种通用的规范，它用统一的XML格式来描述我们生成的机器学习模型。这样无论你的模型是sklearn,R还是Spark MLlib生成的，我们都可以将其转化为标准的XML格式来存储。当我们需要将这个PMML的模型用于部署的时候，可以使用目标环境的解析PMML模型的库来加载模型，并做预测。</p>
<p>可以看出，要使用PMML，需要两步的工作:</p>
<ul>
<li>第一块是将离线训练得到的模型转化为PMML模型文件</li>
<li>第二块是将PMML模型文件载入在线预测环境，进行预测</li>
</ul>
<span id="more"></span>

<h2 id="模型转PMML"><a href="#模型转PMML" class="headerlink" title="模型转PMML"></a>模型转PMML</h2><h3 id="sklearn2pmml"><a href="#sklearn2pmml" class="headerlink" title="sklearn2pmml"></a>sklearn2pmml</h3><p>常见的方法是用<code>sklearn2pmml</code>, 主要思路是将Booster模式转为<code>Scikit-Learn Wrapper interface</code>的<code>XGBModel</code>, 然后再使用<code>PMMLPipeline</code>保存为PMML模型文件</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> xgboost <span class="keyword">as</span> xgb</span><br><span class="line"><span class="keyword">from</span> sklearn_pandas <span class="keyword">import</span> DataFrameMapper</span><br><span class="line"><span class="keyword">from</span> sklearn2pmml <span class="keyword">import</span> PMMLPipeline, sklearn2pmml</span><br><span class="line"></span><br><span class="line">FILE = <span class="string">&#x27;demo.model&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">convert</span>(<span class="params">file</span>):</span><br><span class="line">    mod = xgb.XGBModel()</span><br><span class="line">    mod.load_model(file)</span><br><span class="line">    pipe = PMMLPipeline([</span><br><span class="line">        (<span class="string">&quot;classifier&quot;</span>, mod),</span><br><span class="line">    ])</span><br><span class="line">    <span class="comment"># pipe.configure(ntree_limit=999)</span></span><br><span class="line">    sklearn2pmml(pipe, file + <span class="string">&#x27;.pmml&#x27;</span>, with_repr=<span class="literal">True</span>)</span><br><span class="line">    </span><br><span class="line">convert(FILE)</span><br></pre></td></tr></table></figure>

<p>使用这种方法, 可能会出现因为模型包含中文信息导致转换失败</p>
<figure class="highlight vhdl"><table><tr><td class="code"><pre><span class="line">Standard output <span class="keyword">is</span> empty</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  <span class="keyword">File</span> <span class="string">&quot;&lt;stdin&gt;&quot;</span>, <span class="literal">line</span> <span class="number">7</span>, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">  <span class="keyword">File</span> <span class="string">&quot;/Users/ruan/.pyenv/versions/2.7.16/lib/python2.7/site-packages/sklearn2pmml/__init__.py&quot;</span>, <span class="literal">line</span> <span class="number">262</span>, <span class="keyword">in</span> sklearn2pmml</span><br><span class="line">    print(<span class="string">&quot;Standard error:\n&#123;0&#125;&quot;</span>.format(_decode(<span class="literal">error</span>, java_encoding)))</span><br><span class="line">UnicodeEncodeError: <span class="symbol">&#x27;ascii</span>&#x27; codec can<span class="symbol">&#x27;t</span> encode <span class="built_in">character</span> u&#x27;\u6708&#x27; <span class="keyword">in</span> position <span class="number">1</span>: ordinal <span class="keyword">not</span> <span class="keyword">in</span> <span class="keyword">range</span>(<span class="number">128</span>)</span><br><span class="line"><span class="symbol">&#x27;ascii</span>&#x27; codec can<span class="symbol">&#x27;t</span> encode <span class="built_in">character</span> u&#x27;\u6708&#x27; <span class="keyword">in</span> position <span class="number">1</span>: ordinal <span class="keyword">not</span> <span class="keyword">in</span> <span class="keyword">range</span>(<span class="number">128</span>)</span><br></pre></td></tr></table></figure>

<h3 id="jpmml-xgboost"><a href="#jpmml-xgboost" class="headerlink" title="jpmml-xgboost"></a>jpmml-xgboost</h3><p>也可以<a href="https://github.com/jpmml/jpmml-xgboost">jpmml-xgboost</a>工具, 将xgboost模型直接转换为PMML</p>
<p>注意这里的模型文件, 需要是通过<code>xgboost.Booster.save_model</code>方法保存的.</p>
<h4 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">wget https://github.com/jpmml/jpmml-xgboost/releases/download/1.3.15/jpmml-xgboost-executable-1.3.15.jar</span><br></pre></td></tr></table></figure>

<h4 id="生成fmap特征映射文件"><a href="#生成fmap特征映射文件" class="headerlink" title="生成fmap特征映射文件"></a>生成fmap特征映射文件</h4><p>fmap文件的每行的格式是: <code>特征索引\t特征名称\tq</code><br>特征名称可以随意命名, 在调用PMML预测时会用到</p>
<p>例如: fmap.txt</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="number">0</span>	fea0	<span class="selector-tag">q</span></span><br><span class="line"><span class="number">1</span>	fea1	<span class="selector-tag">q</span></span><br><span class="line"><span class="number">2</span>	fea2	<span class="selector-tag">q</span></span><br><span class="line"><span class="number">3</span>	fea3	<span class="selector-tag">q</span></span><br><span class="line"><span class="number">4</span>	fea4	<span class="selector-tag">q</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<h4 id="执行"><a href="#执行" class="headerlink" title="执行"></a>执行</h4><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">java -jar jpmml-xgboost-executable-1.3.15.jar \</span><br><span class="line">	--model-input demo.model \  # 输入</span><br><span class="line">	--pmml-output demo.model.pmml \  # 输出</span><br><span class="line">	--fmap-input fmap.txt \  # 特征映射文件</span><br><span class="line">	--missing-value -1 \   # 指定模型缺失值</span><br></pre></td></tr></table></figure>

<h2 id="模型预测"><a href="#模型预测" class="headerlink" title="模型预测"></a>模型预测</h2><h3 id="python环境比较模型结果"><a href="#python环境比较模型结果" class="headerlink" title="python环境比较模型结果"></a>python环境比较模型结果</h3><p>python可以使用<code>pypmml</code>库, 测试我们转换好的PMML, 并和原始模型进行分数比较</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> pypmml <span class="keyword">import</span> Model</span><br><span class="line"><span class="keyword">import</span> xgboost <span class="keyword">as</span> xgb</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">xgb_mod = xgb.Booster(model_file=<span class="string">&#x27;demo.model&#x27;</span>)</span><br><span class="line">pmml_mod = Model.load(<span class="string">&#x27;demo.model.pmml&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># float分数比较</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">eq</span>(<span class="params">a, b</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">abs</span>(a - b) &lt; <span class="number">0.00001</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># xgboost预测</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">predict</span>(<span class="params">mod, data</span>):</span><br><span class="line">    fea = np.asarray(data, dtype=np.float64).reshape([<span class="number">1</span>, -<span class="number">1</span>])</span><br><span class="line">    dtest = xgb.DMatrix(fea, missing=np.float64(-<span class="number">1.0</span>))</span><br><span class="line">    <span class="keyword">return</span> mod.predict(dtest)[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># pmml预测</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pmml_predict</span>(<span class="params">mod, data</span>):</span><br><span class="line">    fea = &#123;<span class="string">&quot;fea&#123;&#125;&quot;</span>.<span class="built_in">format</span>(idx): elem <span class="keyword">for</span> (idx, elem) <span class="keyword">in</span>  <span class="built_in">enumerate</span>(data)&#125;</span><br><span class="line">    <span class="keyword">return</span> mod.predict(fea)[<span class="string">&#x27;probability(1)&#x27;</span>]</span><br><span class="line"></span><br><span class="line">data = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line">a, b = predict(xgb_mod, data), pmml_predict(pmml_mod, data)</span><br><span class="line"><span class="keyword">assert</span> eq(a, b)</span><br></pre></td></tr></table></figure>

<h3 id="Java环境执行"><a href="#Java环境执行" class="headerlink" title="Java环境执行"></a>Java环境执行</h3><p>java环境执行, 需要用到<code>pmml-evaluator</code>和<code>pmml-evaluator-extension</code>库</p>
<p>具体代码参考 <a href="https://www.cnblogs.com/pinard/p/9220199.html">https://www.cnblogs.com/pinard/p/9220199.html</a></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://www.cnblogs.com/pinard/p/9220199.html">https://www.cnblogs.com/pinard/p/9220199.html</a></li>
<li><a href="https://blog.csdn.net/yueguanghaidao/article/details/91892549">https://blog.csdn.net/yueguanghaidao/article/details/91892549</a></li>
<li><a href="https://github.com/jpmml/jpmml-xgboost">https://github.com/jpmml/jpmml-xgboost</a></li>
<li><a href="https://yao544303.github.io/2018/07/11/sklearn-PMML/">https://yao544303.github.io/2018/07/11/sklearn-PMML/</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>机器学习</tag>
        <tag>Java</tag>
      </tags>
  </entry>
  <entry>
    <title>布隆过滤器原理及实现</title>
    <url>/2021/06/01/bloom-filter-python-edition/</url>
    <content><![CDATA[<p>布隆过滤器（英语：Bloom Filter）是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。</p>
<p>它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难。</p>
<span id="more"></span>
<h2 id="布隆过滤器原理"><a href="#布隆过滤器原理" class="headerlink" title="布隆过滤器原理"></a>布隆过滤器原理</h2><p>如果想判断一个元素是不是在一个集合里，一般想到的是将集合中所有元素保存起来，然后通过比较确定，比如在Python中通过dict来实现。</p>
<p>使用dict的方案有个局限，就是没办法应用在大规模数据上。</p>
<p>以英语单词为例，我们有三个单词apple，banana，cherry。</p>
<p>如果用dict来存的话是, 查询的准确性是100%，空间占用也是100%<br><img data-src="https://image.ponder.work/mweb/2021-06-01-16225142545801.jpg"></p>
<p>如果我们对准确性要求降低一些，我们可以只记录单词的首字母；<br>首字母不存在dict中的话，该单词不存在，否则有可能存在。<br><img data-src="https://image.ponder.work/mweb/2021-06-01-16225146416983.jpg"></p>
<p>记录首字母的方法虽然有效，但是由于同首字母的单词很多，只记录首字母准确率太低了。<br>所以我们可以依次记录前2个字母，只有这两个字母都存在，我们才认为该单词存在。<br><img data-src="https://image.ponder.work/mweb/2021-06-01-16225149337703.jpg"></p>
<p>以此类推，为了提高准确率，我们可以从前2个字母增加到前n个字母。<br>但是也不能太大，太大的话，dict里的每个字母被重复设置的次数过多，准确率反而会下降。</p>
<p>以上方案还有一个问题，单词中每个字母的分布是很不均匀的，导致准确率对不同的单词也不稳定。<br>可以不直接记录字母，而是对单词应用哈希函数，并将结果按一个数值（比如26）取模，如<code>hash(&#39;apple&#39;) % 26</code>，当做一个单词的一个字母来记录。<br>同时应用多个不同的哈希函数，并记录取模后的值，相当于记录该单词的多个字母。</p>
<h2 id="Python实现"><a href="#Python实现" class="headerlink" title="Python实现"></a>Python实现</h2><p>根据上文的分析，可以实现Python版本的布隆过滤器。<br>用<a href="/2021/05/31/bitmap-python-edition/">bitmap</a>代替dict，节省空间占用。<br>使用Python内置的<code>hash</code>函数作为哈希函数。<br>哈希前对元素增加不同的后缀再调用，替代理论里的多个不同哈希函数。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">BIT_SIZE = <span class="number">5000000</span></span><br><span class="line">BYTE_WIDTH = <span class="number">8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BitMap</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, size, fill=<span class="number">0</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>._array = <span class="built_in">bytearray</span>((fill <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(size//BYTE_WIDTH+<span class="number">1</span>)))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">set</span>(<span class="params">self, index</span>):</span><br><span class="line">        major, minor = <span class="built_in">divmod</span>(index, BYTE_WIDTH)</span><br><span class="line">        <span class="variable language_">self</span>._array[major] |= (<span class="number">0b10000000</span> &gt;&gt; minor)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get</span>(<span class="params">self, index</span>):</span><br><span class="line">        major, minor = <span class="built_in">divmod</span>(index, BYTE_WIDTH)</span><br><span class="line">        mask = <span class="number">0b10000000</span> &gt;&gt; minor</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">int</span>(<span class="variable language_">self</span>._array[major] &amp; mask == mask)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BloomFilter</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.bit_array = BitMap(BIT_SIZE)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add</span>(<span class="params">self, url</span>):</span><br><span class="line">        point_list = <span class="variable language_">self</span>.get_postions(url)</span><br><span class="line">        <span class="keyword">for</span> b <span class="keyword">in</span> point_list:</span><br><span class="line">            <span class="variable language_">self</span>.bit_array.<span class="built_in">set</span>(b)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">contains</span>(<span class="params">self, url</span>):</span><br><span class="line">        point_list = <span class="variable language_">self</span>.get_postions(url)</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">all</span>(<span class="variable language_">self</span>.bit_array.get(i) <span class="keyword">for</span> i <span class="keyword">in</span> point_list)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_postions</span>(<span class="params">self, url</span>):</span><br><span class="line">        <span class="keyword">return</span> [<span class="built_in">hash</span>(<span class="string">&#x27;&#123;&#125;-&#123;&#125;&#x27;</span>.<span class="built_in">format</span>(url, i)) % BIT_SIZE <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">41</span>, <span class="number">48</span>)]</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    bf = BloomFilter()</span><br><span class="line">    bf.add(<span class="string">&#x27;1&#x27;</span>)</span><br><span class="line">    <span class="built_in">print</span>(bf.contains(<span class="string">&#x27;1&#x27;</span>))</span><br><span class="line">    <span class="built_in">print</span>(bf.contains(<span class="string">&#x27;2&#x27;</span>))</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://zh.wikipedia.org/wiki/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8">https://zh.wikipedia.org/wiki/布隆过滤器</a></li>
<li><a href="https://www.cnblogs.com/cpselvis/p/6265825.html">https://www.cnblogs.com/cpselvis/p/6265825.html</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>数据结构</tag>
      </tags>
  </entry>
  <entry>
    <title>囚徒健身笔记</title>
    <url>/2021/06/10/convict-conditioning/</url>
    <content><![CDATA[<p>这不是一本教你练出“可爱肌肉”的书，而是一本教你练出能用的力量、极限的力量、生存的力量的书。<br>作者保罗·威德在美国最严酷的监狱中度过了19年，在其中逐渐挖掘出了一套最古老的健身法，在商业社会中早已失传的力量哲学，并凭此成为了地球上最强壮的人之一。</p>
<p>本文是用于速查囚徒健身”六艺”的手册</p>
<span id="more"></span>

<h2 id="俯卧撑"><a href="#俯卧撑" class="headerlink" title="俯卧撑"></a>俯卧撑</h2><h3 id="升级表"><a href="#升级表" class="headerlink" title="升级表"></a>升级表</h3><table>
<thead>
<tr>
<th></th>
<th>名称</th>
<th>逐步做到</th>
</tr>
</thead>
<tbody><tr>
<td>第一式</td>
<td>墙壁俯卧撑</td>
<td>3 x 50 次</td>
</tr>
<tr>
<td>第二式</td>
<td>上斜俯卧撑</td>
<td>3 x 40 次</td>
</tr>
<tr>
<td>第三式</td>
<td>膝盖俯卧撑</td>
<td>3 x 30 次</td>
</tr>
<tr>
<td>第四式</td>
<td>半俯卧撑</td>
<td>2 x 25 次</td>
</tr>
<tr>
<td>第五式</td>
<td>标准俯卧撑</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第六式</td>
<td>窄距俯卧撑</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第七式</td>
<td>偏重俯卧撑</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第八式</td>
<td>单臂半俯卧撑</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第九式</td>
<td>杠杆俯卧撑</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>最终式</td>
<td>单臂俯卧撑</td>
<td>1 x 100 次</td>
</tr>
</tbody></table>
<h3 id="第一式-墙壁俯卧撑"><a href="#第一式-墙壁俯卧撑" class="headerlink" title="第一式 墙壁俯卧撑"></a>第一式 墙壁俯卧撑</h3><p><img data-src="/media/2021-06-12-16233763110637.jpg"><br><img data-src="/media/2021-06-12-16233763151918.jpg"></p>
<h3 id="第二式-上斜俯卧撑"><a href="#第二式-上斜俯卧撑" class="headerlink" title="第二式 上斜俯卧撑"></a>第二式 上斜俯卧撑</h3><p><img data-src="/media/2021-06-12-16233763579987.jpg"><br><img data-src="/media/2021-06-12-16233763621816.jpg"></p>
<h3 id="第三式-膝盖俯卧撑"><a href="#第三式-膝盖俯卧撑" class="headerlink" title="第三式 膝盖俯卧撑"></a>第三式 膝盖俯卧撑</h3><p><img data-src="/media/2021-06-12-16233763890571.jpg"><br><img data-src="/media/2021-06-12-16233763946028.jpg"></p>
<h3 id="第四式-半俯卧撑"><a href="#第四式-半俯卧撑" class="headerlink" title="第四式 半俯卧撑"></a>第四式 半俯卧撑</h3><p><img data-src="/media/2021-06-12-16233764161292.jpg"><br><img data-src="/media/2021-06-12-16233764200771.jpg"></p>
<h3 id="第五式-标准俯卧撑"><a href="#第五式-标准俯卧撑" class="headerlink" title="第五式 标准俯卧撑"></a>第五式 标准俯卧撑</h3><p><img data-src="/media/2021-06-12-16233764425831.jpg"><br><img data-src="/media/2021-06-12-16233764478111.jpg"></p>
<h3 id="第六式-窄距俯卧撑"><a href="#第六式-窄距俯卧撑" class="headerlink" title="第六式 窄距俯卧撑"></a>第六式 窄距俯卧撑</h3><p><img data-src="/media/2021-06-12-16233769560037.jpg"><br><img data-src="/media/2021-06-12-16233769605101.jpg"></p>
<h3 id="第七式-偏重俯卧撑"><a href="#第七式-偏重俯卧撑" class="headerlink" title="第七式 偏重俯卧撑"></a>第七式 偏重俯卧撑</h3><p><img data-src="/media/2021-06-12-16233769968660.jpg"><br><img data-src="/media/2021-06-12-16233770012414.jpg"></p>
<h3 id="第八式-单臂半俯卧撑"><a href="#第八式-单臂半俯卧撑" class="headerlink" title="第八式 单臂半俯卧撑"></a>第八式 单臂半俯卧撑</h3><p><img data-src="/media/2021-06-12-16233770432550.jpg"><br><img data-src="/media/2021-06-12-16233770509249.jpg"></p>
<h3 id="第九式-杠杆俯卧撑"><a href="#第九式-杠杆俯卧撑" class="headerlink" title="第九式 杠杆俯卧撑"></a>第九式 杠杆俯卧撑</h3><p><img data-src="/media/2021-06-12-16233770802445.jpg"><br><img data-src="/media/2021-06-12-16233770843751.jpg"></p>
<h3 id="最终式-单臂俯卧撑"><a href="#最终式-单臂俯卧撑" class="headerlink" title="最终式 单臂俯卧撑"></a>最终式 单臂俯卧撑</h3><p><img data-src="/media/2021-06-12-16233771031421.jpg"><br><img data-src="/media/2021-06-12-16233771091907.jpg"></p>
<h2 id="深蹲"><a href="#深蹲" class="headerlink" title="深蹲"></a>深蹲</h2><h3 id="升级表-1"><a href="#升级表-1" class="headerlink" title="升级表"></a>升级表</h3><table>
<thead>
<tr>
<th></th>
<th>名称</th>
<th>逐步做到</th>
</tr>
</thead>
<tbody><tr>
<td>第一式</td>
<td>肩倒立深蹲</td>
<td>3 x 50 次</td>
</tr>
<tr>
<td>第二式</td>
<td>折刀深蹲</td>
<td>3 x 40 次</td>
</tr>
<tr>
<td>第三式</td>
<td>支撑深蹲</td>
<td>3 x 30 次</td>
</tr>
<tr>
<td>第四式</td>
<td>半深蹲</td>
<td>2 x 50 次</td>
</tr>
<tr>
<td>第五式</td>
<td>标准深蹲</td>
<td>2 x 30 次</td>
</tr>
<tr>
<td>第六式</td>
<td>窄距深蹲</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第七式</td>
<td>偏重深蹲</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第八式</td>
<td>单腿半深蹲</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第九式</td>
<td>单腿辅助深蹲</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>最终式</td>
<td>单腿深蹲</td>
<td>2 x 50 次</td>
</tr>
</tbody></table>
<h3 id="第一式-肩倒立深蹲"><a href="#第一式-肩倒立深蹲" class="headerlink" title="第一式 肩倒立深蹲"></a>第一式 肩倒立深蹲</h3><p><img data-src="/media/2021-06-12-16233772675490.jpg"><br><img data-src="/media/2021-06-12-16233772719098.jpg"></p>
<h3 id="第二式-折刀深蹲"><a href="#第二式-折刀深蹲" class="headerlink" title="第二式 折刀深蹲"></a>第二式 折刀深蹲</h3><p><img data-src="/media/2021-06-12-16233772955163.jpg"><br><img data-src="/media/2021-06-12-16233772994497.jpg"></p>
<h3 id="第三式-支撑深蹲"><a href="#第三式-支撑深蹲" class="headerlink" title="第三式 支撑深蹲"></a>第三式 支撑深蹲</h3><p><img data-src="/media/2021-06-12-16233773213387.jpg"><br><img data-src="/media/2021-06-12-16233773261167.jpg"></p>
<h3 id="第四式-半深蹲"><a href="#第四式-半深蹲" class="headerlink" title="第四式 半深蹲"></a>第四式 半深蹲</h3><p><img data-src="/media/2021-06-12-16233773506567.jpg"><br><img data-src="/media/2021-06-12-16233773550078.jpg"></p>
<h3 id="第五式-标准深蹲"><a href="#第五式-标准深蹲" class="headerlink" title="第五式 标准深蹲"></a>第五式 标准深蹲</h3><p><img data-src="/media/2021-06-12-16233773844689.jpg"><br><img data-src="/media/2021-06-12-16233773897676.jpg"></p>
<h3 id="第六式-窄距深蹲"><a href="#第六式-窄距深蹲" class="headerlink" title="第六式 窄距深蹲"></a>第六式 窄距深蹲</h3><p><img data-src="/media/2021-06-12-16233774176578.jpg"><br><img data-src="/media/2021-06-12-16233774216603.jpg"></p>
<h3 id="第七式-偏重深蹲"><a href="#第七式-偏重深蹲" class="headerlink" title="第七式 偏重深蹲"></a>第七式 偏重深蹲</h3><p><img data-src="/media/2021-06-12-16233774451041.jpg"><br><img data-src="/media/2021-06-12-16233774620446.jpg"></p>
<h3 id="第八式-单腿半深蹲"><a href="#第八式-单腿半深蹲" class="headerlink" title="第八式 单腿半深蹲"></a>第八式 单腿半深蹲</h3><p><img data-src="/media/2021-06-12-16233774812360.jpg"><br><img data-src="/media/2021-06-12-16233774886667.jpg"></p>
<h3 id="第九式-单腿辅助深蹲"><a href="#第九式-单腿辅助深蹲" class="headerlink" title="第九式 单腿辅助深蹲"></a>第九式 单腿辅助深蹲</h3><p><img data-src="/media/2021-06-12-16233775041579.jpg"><br><img data-src="/media/2021-06-12-16233775094918.jpg"></p>
<h3 id="最终式-单腿深蹲"><a href="#最终式-单腿深蹲" class="headerlink" title="最终式 单腿深蹲"></a>最终式 单腿深蹲</h3><p><img data-src="/media/2021-06-12-16233775591993.jpg"><br><img data-src="/media/2021-06-12-16233775633887.jpg"></p>
<h2 id="引体向上"><a href="#引体向上" class="headerlink" title="引体向上"></a>引体向上</h2><h3 id="升级表-2"><a href="#升级表-2" class="headerlink" title="升级表"></a>升级表</h3><table>
<thead>
<tr>
<th></th>
<th>名称</th>
<th>逐步做到</th>
</tr>
</thead>
<tbody><tr>
<td>第一式</td>
<td>垂直引体</td>
<td>3 x 40 次</td>
</tr>
<tr>
<td>第二式</td>
<td>水平引体向上</td>
<td>3 x 30 次</td>
</tr>
<tr>
<td>第三式</td>
<td>折刀引体向上</td>
<td>3 x 20 次</td>
</tr>
<tr>
<td>第四式</td>
<td>半引体向上</td>
<td>2 x 15 次</td>
</tr>
<tr>
<td>第五式</td>
<td>标准引体向上</td>
<td>2 x 10 次</td>
</tr>
<tr>
<td>第六式</td>
<td>窄距引体向上</td>
<td>2 x 10 次</td>
</tr>
<tr>
<td>第七式</td>
<td>偏重引体向上</td>
<td>2 x 9 次</td>
</tr>
<tr>
<td>第八式</td>
<td>单臂半引体向上</td>
<td>2 x 8 次</td>
</tr>
<tr>
<td>第九式</td>
<td>单臂辅助引体向上</td>
<td>2 x 7 次</td>
</tr>
<tr>
<td>最终式</td>
<td>单臂引体向上</td>
<td>2 x 6 次</td>
</tr>
</tbody></table>
<h3 id="第一式-垂直引体"><a href="#第一式-垂直引体" class="headerlink" title="第一式 垂直引体"></a>第一式 垂直引体</h3><p><img data-src="/media/2021-06-12-16235046931778.jpg"><br><img data-src="/media/2021-06-12-16235046978279.jpg"></p>
<h3 id="第二式-水平引体向上"><a href="#第二式-水平引体向上" class="headerlink" title="第二式 水平引体向上"></a>第二式 水平引体向上</h3><p><img data-src="/media/2021-06-12-16235047167487.jpg"><br><img data-src="/media/2021-06-12-16235047206632.jpg"></p>
<h3 id="第三式-折刀引体向上"><a href="#第三式-折刀引体向上" class="headerlink" title="第三式 折刀引体向上"></a>第三式 折刀引体向上</h3><p><img data-src="/media/2021-06-12-16235047427692.jpg"><br><img data-src="/media/2021-06-12-16235047509050.jpg"></p>
<h3 id="第四式-半引体向上"><a href="#第四式-半引体向上" class="headerlink" title="第四式 半引体向上"></a>第四式 半引体向上</h3><p><img data-src="/media/2021-06-12-16235047741587.jpg"><br><img data-src="/media/2021-06-12-16235047788361.jpg"></p>
<h3 id="第五式-标准引体向上"><a href="#第五式-标准引体向上" class="headerlink" title="第五式 标准引体向上"></a>第五式 标准引体向上</h3><p><img data-src="/media/2021-06-12-16235050886345.jpg"><br><img data-src="/media/2021-06-12-16235050937641.jpg"></p>
<h3 id="第六式-窄距引体向上"><a href="#第六式-窄距引体向上" class="headerlink" title="第六式 窄距引体向上"></a>第六式 窄距引体向上</h3><p><img data-src="/media/2021-06-12-16235052213410.jpg"><br><img data-src="/media/2021-06-12-16235052268650.jpg"></p>
<h3 id="第七式-偏重引体向上"><a href="#第七式-偏重引体向上" class="headerlink" title="第七式 偏重引体向上"></a>第七式 偏重引体向上</h3><p><img data-src="/media/2021-06-12-16235052582706.jpg"><br><img data-src="/media/2021-06-12-16235052625014.jpg"></p>
<h3 id="第八式-单臂半引体向上"><a href="#第八式-单臂半引体向上" class="headerlink" title="第八式 单臂半引体向上"></a>第八式 单臂半引体向上</h3><p><img data-src="/media/2021-06-12-16235052816468.jpg"><br><img data-src="/media/2021-06-12-16235052856461.jpg"></p>
<h3 id="第九式-单臂辅助引体向上"><a href="#第九式-单臂辅助引体向上" class="headerlink" title="第九式 单臂辅助引体向上"></a>第九式 单臂辅助引体向上</h3><p><img data-src="/media/2021-06-12-16235053207198.jpg"><br><img data-src="/media/2021-06-12-16235053249302.jpg"></p>
<h3 id="最终式-单臂引体向上"><a href="#最终式-单臂引体向上" class="headerlink" title="最终式 单臂引体向上"></a>最终式 单臂引体向上</h3><p><img data-src="/media/2021-06-12-16235056128115.jpg"><br><img data-src="/media/2021-06-12-16235056169086.jpg"></p>
<h2 id="举腿"><a href="#举腿" class="headerlink" title="举腿"></a>举腿</h2><h3 id="升级表-3"><a href="#升级表-3" class="headerlink" title="升级表"></a>升级表</h3><table>
<thead>
<tr>
<th></th>
<th>名称</th>
<th>逐步做到</th>
</tr>
</thead>
<tbody><tr>
<td>第一式</td>
<td>坐姿屈膝</td>
<td>3 x 40 次</td>
</tr>
<tr>
<td>第二式</td>
<td>平卧抬膝</td>
<td>3 x 35 次</td>
</tr>
<tr>
<td>第三式</td>
<td>平卧屈举腿</td>
<td>3 x 30 次</td>
</tr>
<tr>
<td>第四式</td>
<td>平卧蛙举腿</td>
<td>2 x 25 次</td>
</tr>
<tr>
<td>第五式</td>
<td>平卧直举腿</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第六式</td>
<td>悬垂屈膝</td>
<td>2 x 15 次</td>
</tr>
<tr>
<td>第七式</td>
<td>悬垂屈举腿</td>
<td>2 x 15 次</td>
</tr>
<tr>
<td>第八式</td>
<td>悬垂蛙举腿</td>
<td>2 x 15 次</td>
</tr>
<tr>
<td>第九式</td>
<td>悬垂半举腿</td>
<td>2 x 15 次</td>
</tr>
<tr>
<td>最终式</td>
<td>悬垂直举腿</td>
<td>2 x 30 次</td>
</tr>
</tbody></table>
<h3 id="第一式-坐姿屈膝"><a href="#第一式-坐姿屈膝" class="headerlink" title="第一式 坐姿屈膝"></a>第一式 坐姿屈膝</h3><p><img data-src="/media/2021-06-12-16235058330516.jpg"><br><img data-src="/media/2021-06-12-16235058387786.jpg"></p>
<h3 id="第二式-平卧抬膝"><a href="#第二式-平卧抬膝" class="headerlink" title="第二式 平卧抬膝"></a>第二式 平卧抬膝</h3><p><img data-src="/media/2021-06-12-16235058715628.jpg"><br><img data-src="/media/2021-06-12-16235058987367.jpg"></p>
<h3 id="第三式-平卧屈举腿"><a href="#第三式-平卧屈举腿" class="headerlink" title="第三式 平卧屈举腿"></a>第三式 平卧屈举腿</h3><p><img data-src="/media/2021-06-12-16235059377778.jpg"><br><img data-src="/media/2021-06-12-16235059420733.jpg"></p>
<h3 id="第四式-平卧蛙举腿"><a href="#第四式-平卧蛙举腿" class="headerlink" title="第四式 平卧蛙举腿"></a>第四式 平卧蛙举腿</h3><p><img data-src="/media/2021-06-12-16235059751819.jpg"><br><img data-src="/media/2021-06-12-16235059808055.jpg"><br><img data-src="/media/2021-06-12-16235059845183.jpg"></p>
<h3 id="第五式-平卧直举腿"><a href="#第五式-平卧直举腿" class="headerlink" title="第五式 平卧直举腿"></a>第五式 平卧直举腿</h3><p><img data-src="/media/2021-06-12-16235060043259.jpg"><br><img data-src="/media/2021-06-12-16235060079971.jpg"></p>
<h3 id="第六式-悬垂屈膝"><a href="#第六式-悬垂屈膝" class="headerlink" title="第六式 悬垂屈膝"></a>第六式 悬垂屈膝</h3><p><img data-src="/media/2021-06-12-16235060276364.jpg"><br><img data-src="/media/2021-06-12-16235060318027.jpg"></p>
<h3 id="第七式-悬垂屈举腿"><a href="#第七式-悬垂屈举腿" class="headerlink" title="第七式 悬垂屈举腿"></a>第七式 悬垂屈举腿</h3><p><img data-src="/media/2021-06-12-16235060762848.jpg"><br><img data-src="/media/2021-06-12-16235060798627.jpg"></p>
<h3 id="第八式-悬垂蛙举腿"><a href="#第八式-悬垂蛙举腿" class="headerlink" title="第八式 悬垂蛙举腿"></a>第八式 悬垂蛙举腿</h3><p><img data-src="/media/2021-06-12-16235061418479.jpg"><br><img data-src="/media/2021-06-12-16235061459126.jpg"></p>
<h3 id="第九式-悬垂半举腿"><a href="#第九式-悬垂半举腿" class="headerlink" title="第九式 悬垂半举腿"></a>第九式 悬垂半举腿</h3><p><img data-src="/media/2021-06-12-16235061942966.jpg"><br><img data-src="/media/2021-06-12-16235061990087.jpg"></p>
<h3 id="最终式-悬垂直举腿"><a href="#最终式-悬垂直举腿" class="headerlink" title="最终式 悬垂直举腿"></a>最终式 悬垂直举腿</h3><p><img data-src="/media/2021-06-12-16235062300592.jpg"><br><img data-src="/media/2021-06-12-16235062342441.jpg"></p>
<h2 id="桥"><a href="#桥" class="headerlink" title="桥"></a>桥</h2><h3 id="升级表-4"><a href="#升级表-4" class="headerlink" title="升级表"></a>升级表</h3><table>
<thead>
<tr>
<th></th>
<th>名称</th>
<th>逐步做到</th>
</tr>
</thead>
<tbody><tr>
<td>第一式</td>
<td>短桥</td>
<td>3 x 50 次</td>
</tr>
<tr>
<td>第二式</td>
<td>直桥</td>
<td>3 x 40 次</td>
</tr>
<tr>
<td>第三式</td>
<td>高低桥</td>
<td>3 x 30 次</td>
</tr>
<tr>
<td>第四式</td>
<td>顶桥</td>
<td>2 x 25 次</td>
</tr>
<tr>
<td>第五式</td>
<td>半桥</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第六式</td>
<td>标准桥</td>
<td>2 x 15 次</td>
</tr>
<tr>
<td>第七式</td>
<td>下行桥</td>
<td>2 x 10 次</td>
</tr>
<tr>
<td>第八式</td>
<td>上行桥</td>
<td>2 x 8 次</td>
</tr>
<tr>
<td>第九式</td>
<td>合桥</td>
<td>2 x 6 次</td>
</tr>
<tr>
<td>最终式</td>
<td>铁板桥</td>
<td>2 x 30 次</td>
</tr>
</tbody></table>
<h3 id="第一式-短桥"><a href="#第一式-短桥" class="headerlink" title="第一式 短桥"></a>第一式 短桥</h3><p><img data-src="/media/2021-06-12-16235063290015.jpg"><br><img data-src="/media/2021-06-12-16235063328476.jpg"></p>
<h3 id="第二式-直桥"><a href="#第二式-直桥" class="headerlink" title="第二式 直桥"></a>第二式 直桥</h3><p><img data-src="/media/2021-06-12-16235063942389.jpg"><br><img data-src="/media/2021-06-12-16235064080533.jpg"></p>
<h3 id="第三式-高低桥"><a href="#第三式-高低桥" class="headerlink" title="第三式 高低桥"></a>第三式 高低桥</h3><p><img data-src="/media/2021-06-12-16235064277856.jpg"><br><img data-src="/media/2021-06-12-16235064317107.jpg"></p>
<h3 id="第四式-顶桥"><a href="#第四式-顶桥" class="headerlink" title="第四式 顶桥"></a>第四式 顶桥</h3><p><img data-src="/media/2021-06-12-16235064592857.jpg"><br><img data-src="/media/2021-06-12-16235064626259.jpg"></p>
<h3 id="第五式-半桥"><a href="#第五式-半桥" class="headerlink" title="第五式 半桥"></a>第五式 半桥</h3><p><img data-src="/media/2021-06-12-16235065659440.jpg"><br><img data-src="/media/2021-06-12-16235065703449.jpg"></p>
<h3 id="第六式-标准桥"><a href="#第六式-标准桥" class="headerlink" title="第六式 标准桥"></a>第六式 标准桥</h3><p><img data-src="/media/2021-06-12-16235066089725.jpg"><br><img data-src="/media/2021-06-12-16235066129116.jpg"></p>
<h3 id="第七式-下行桥"><a href="#第七式-下行桥" class="headerlink" title="第七式 下行桥"></a>第七式 下行桥</h3><p><img data-src="/media/2021-06-12-16235066913378.jpg"></p>
<h3 id="第八式-上行桥"><a href="#第八式-上行桥" class="headerlink" title="第八式 上行桥"></a>第八式 上行桥</h3><p><img data-src="/media/2021-06-12-16235067177964.jpg"></p>
<h3 id="第九式-合桥"><a href="#第九式-合桥" class="headerlink" title="第九式 合桥"></a>第九式 合桥</h3><p><img data-src="/media/2021-06-12-16235067415816.jpg"></p>
<h3 id="最终式-铁板桥"><a href="#最终式-铁板桥" class="headerlink" title="最终式 铁板桥"></a>最终式 铁板桥</h3><p><img data-src="/media/2021-06-12-16235067900645.jpg"></p>
<h2 id="倒立撑"><a href="#倒立撑" class="headerlink" title="倒立撑"></a>倒立撑</h2><h3 id="升级表-5"><a href="#升级表-5" class="headerlink" title="升级表"></a>升级表</h3><table>
<thead>
<tr>
<th></th>
<th>名称</th>
<th>逐步做到</th>
</tr>
</thead>
<tbody><tr>
<td>第一式</td>
<td>靠墙顶立</td>
<td>2 分钟</td>
</tr>
<tr>
<td>第二式</td>
<td>乌鸦式</td>
<td>1 分钟</td>
</tr>
<tr>
<td>第三式</td>
<td>靠墙倒立</td>
<td>2 分钟</td>
</tr>
<tr>
<td>第四式</td>
<td>半倒立撑</td>
<td>2 x 20 次</td>
</tr>
<tr>
<td>第五式</td>
<td>标准倒立撑</td>
<td>2 x 15 次</td>
</tr>
<tr>
<td>第六式</td>
<td>窄距倒立撑</td>
<td>2 x 12 次</td>
</tr>
<tr>
<td>第七式</td>
<td>偏重倒立撑</td>
<td>2 x 10 次</td>
</tr>
<tr>
<td>第八式</td>
<td>单臂半倒立撑</td>
<td>2 x 8 次</td>
</tr>
<tr>
<td>第九式</td>
<td>杠杆倒立撑</td>
<td>2 x 6 次</td>
</tr>
<tr>
<td>最终式</td>
<td>单臂倒立撑</td>
<td>2 x 5 次</td>
</tr>
</tbody></table>
<h3 id="第一式-靠墙顶立"><a href="#第一式-靠墙顶立" class="headerlink" title="第一式 靠墙顶立"></a>第一式 靠墙顶立</h3><p><img data-src="/media/2021-06-12-16235068440926.jpg"><br><img data-src="/media/2021-06-12-16235068489541.jpg"></p>
<h3 id="第二式-乌鸦式"><a href="#第二式-乌鸦式" class="headerlink" title="第二式 乌鸦式"></a>第二式 乌鸦式</h3><p><img data-src="/media/2021-06-12-16235068745842.jpg"></p>
<h3 id="第三式-靠墙倒立"><a href="#第三式-靠墙倒立" class="headerlink" title="第三式 靠墙倒立"></a>第三式 靠墙倒立</h3><p><img data-src="/media/2021-06-12-16235068887754.jpg"><br><img data-src="/media/2021-06-12-16235069018339.jpg"></p>
<h3 id="第四式-半倒立撑"><a href="#第四式-半倒立撑" class="headerlink" title="第四式 半倒立撑"></a>第四式 半倒立撑</h3><p><img data-src="/media/2021-06-12-16235069405758.jpg"></p>
<h3 id="第五式-标准倒立撑"><a href="#第五式-标准倒立撑" class="headerlink" title="第五式 标准倒立撑"></a>第五式 标准倒立撑</h3><p><img data-src="/media/2021-06-12-16235069592851.jpg"></p>
<h3 id="第六式-窄距倒立撑"><a href="#第六式-窄距倒立撑" class="headerlink" title="第六式 窄距倒立撑"></a>第六式 窄距倒立撑</h3><p><img data-src="/media/2021-06-12-16235069787787.jpg"></p>
<h3 id="第七式-偏重倒立撑"><a href="#第七式-偏重倒立撑" class="headerlink" title="第七式 偏重倒立撑"></a>第七式 偏重倒立撑</h3><p><img data-src="/media/2021-06-12-16235069957421.jpg"></p>
<h3 id="第八式-单臂半倒立撑"><a href="#第八式-单臂半倒立撑" class="headerlink" title="第八式 单臂半倒立撑"></a>第八式 单臂半倒立撑</h3><p><img data-src="/media/2021-06-12-16235070206862.jpg"></p>
<h3 id="第九式-杠杆倒立撑"><a href="#第九式-杠杆倒立撑" class="headerlink" title="第九式 杠杆倒立撑"></a>第九式 杠杆倒立撑</h3><p><img data-src="/media/2021-06-12-16235070497871.jpg"></p>
<h3 id="最终式-单臂倒立撑"><a href="#最终式-单臂倒立撑" class="headerlink" title="最终式 单臂倒立撑"></a>最终式 单臂倒立撑</h3><p><img data-src="/media/2021-06-12-16235070847276.jpg"></p>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>健身</tag>
        <tag>GTD</tag>
      </tags>
  </entry>
  <entry>
    <title>使用gdb调试Python程序</title>
    <url>/2020/12/29/debug-python-with-gdb/</url>
    <content><![CDATA[<p>由于Python解释器是由C语言编写，我们可以使用GDB来调试Python进程，对于程序卡死等异常情况调试比较有帮助。</p>
<span id="more"></span>

<p>用gdb调试Python程序，主要有两个部分</p>
<ol>
<li>原生的gdb命令，调试的的Python解释器的C代码</li>
<li><code>py-bt</code>等以py-为前缀的Python扩展命令，可以调试Python程序</li>
</ol>
<p>我们需要通过原生gdb命令，如<code>n</code>(next)，<code>b</code>(break)等，使Python程序运行到我们需要调试的位置。<br>然后，通过<code>py-print</code>等命令输出我们想要的变量信息</p>
<h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><p>不同的Python环境，配置方法不太一样，这里推荐ubuntu 20.04以上版本</p>
<h3 id="ubuntu-20-04"><a href="#ubuntu-20-04" class="headerlink" title="ubuntu 20.04"></a>ubuntu 20.04</h3><figure class="highlight vim"><table><tr><td class="code"><pre><span class="line">sudo apt install gdb <span class="keyword">python3</span> <span class="keyword">python3</span>-dbg</span><br></pre></td></tr></table></figure>

<h2 id="gdb命令速查"><a href="#gdb命令速查" class="headerlink" title="gdb命令速查"></a>gdb命令速查</h2><h3 id="gdb原生命令"><a href="#gdb原生命令" class="headerlink" title="gdb原生命令"></a>gdb原生命令</h3><p>run or r –&gt; executes the program from start to end.<br>break or b –&gt; sets breakpoint on a particular line.<br>disable -&gt; disable a breakpoint.<br>enable –&gt; enable a disabled breakpoint.<br>next or n -&gt; executes next line of code, but don’t dive into functions.<br>step –&gt; go to next instruction, diving into the function.<br>list or l –&gt; displays the code.<br>print or p –&gt; used to display the stored value.<br>quit or q –&gt; exits out of gdb.<br>clear –&gt; to clear all breakpoints.<br>continue –&gt; continue normal execution.</p>
<h3 id="gdb-python命令"><a href="#gdb-python命令" class="headerlink" title="gdb python命令"></a>gdb python命令</h3><p>py-bt: 输出Python调用栈<br>py-bt-full: 输出Python调用栈<br>py-down: 在调用栈向下一级<br>py-list: 显示代码<br>py-locals: 输出locals变量<br>py-print: 输出<br>py-up: 在调用栈向上一级</p>
<h2 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><h3 id="测试代码"><a href="#测试代码" class="headerlink" title="测试代码"></a>测试代码</h3><p>一个斐波那契数列函数<code>fib.py</code></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fib</span>(<span class="params">n</span>):</span><br><span class="line">    time.sleep(<span class="number">0.01</span>)</span><br><span class="line">    <span class="keyword">if</span> n == <span class="number">1</span> <span class="keyword">or</span> n == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">        <span class="keyword">return</span> fib(n-<span class="number">1</span>) + fib(n-<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">fib(<span class="number">100</span>)</span><br></pre></td></tr></table></figure>

<h3 id="启动程序"><a href="#启动程序" class="headerlink" title="启动程序"></a>启动程序</h3><ol>
<li><code>python3 fib.py &amp;</code></li>
<li><code>gdb python3 148</code>, 148为Python的进程id</li>
</ol>
<p>gdb输出，注意所需要的symbols是否都加载了</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line">GNU gdb (Ubuntu <span class="number">9.2</span>-<span class="number">0</span>ubuntu1~<span class="number">20.04</span>) <span class="number">9.2</span></span><br><span class="line">Copyright (C) <span class="number">2020</span> Free Software Foundation, Inc.</span><br><span class="line">License GPLv3+: GNU GPL version <span class="number">3</span> or later &lt;http:<span class="comment">//gnu.org/licenses/gpl.html&gt;</span></span><br><span class="line"><span class="keyword">This</span> is free software: you are free to change and redistribute it.</span><br><span class="line">There is NO WARRANTY, to the extent permitted by law.</span><br><span class="line">Type <span class="string">&quot;show copying&quot;</span> and <span class="string">&quot;show warranty&quot;</span> <span class="keyword">for</span> details.</span><br><span class="line"><span class="keyword">This</span> GDB was configured as <span class="string">&quot;x86_64-linux-gnu&quot;</span>.</span><br><span class="line">Type <span class="string">&quot;show configuration&quot;</span> <span class="keyword">for</span> configuration details.</span><br><span class="line"><span class="keyword">For</span> bug reporting instructions, please see:</span><br><span class="line">&lt;http:<span class="comment">//www.gnu.org/software/gdb/bugs/&gt;.</span></span><br><span class="line"><span class="keyword">Find</span> the GDB manual and other documentation resources online at:</span><br><span class="line">    &lt;http:<span class="comment">//www.gnu.org/software/gdb/documentation/&gt;.</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">For</span> help, type <span class="string">&quot;help&quot;</span>.</span><br><span class="line">Type <span class="string">&quot;apropos word&quot;</span> to search <span class="keyword">for</span> commands related to <span class="string">&quot;word&quot;</span>...</span><br><span class="line">Reading symbols <span class="keyword">from</span> python3...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/usr/</span>lib<span class="regexp">/debug/</span>.build-id<span class="regexp">/02/</span><span class="number">526282</span>ea6c4d6eec743ad74a1eeefd035346a3.debug...</span><br><span class="line">Attaching to program: <span class="regexp">/usr/</span>bin/python3, process <span class="number">148</span></span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/lib/</span>x86_64-linux-gnu/libc.so.<span class="number">6</span>...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/usr/</span>lib<span class="regexp">/debug/</span><span class="regexp">/lib/</span>x86_64-linux-gnu/libc-<span class="number">2.31</span>.so...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/lib/</span>x86_64-linux-gnu/libpthread.so.<span class="number">0</span>...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/usr/</span>lib<span class="regexp">/debug/</span>.build-id<span class="regexp">/4f/</span>c5fc33f4429136a494c640b113d76f610e4abc.debug...</span><br><span class="line">[Thread debugging using libthread_db enabled]</span><br><span class="line">Using host libthread_db library <span class="string">&quot;/lib/x86_64-linux-gnu/libthread_db.so.1&quot;</span>.</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/lib/</span>x86_64-linux-gnu/libdl.so.<span class="number">2</span>...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/usr/</span>lib<span class="regexp">/debug/</span><span class="regexp">/lib/</span>x86_64-linux-gnu/libdl-<span class="number">2.31</span>.so...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/lib/</span>x86_64-linux-gnu/libutil.so.<span class="number">1</span>...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/usr/</span>lib<span class="regexp">/debug/</span><span class="regexp">/lib/</span>x86_64-linux-gnu/libutil-<span class="number">2.31</span>.so...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/lib/</span>x86_64-linux-gnu/libm.so.<span class="number">6</span>...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/usr/</span>lib<span class="regexp">/debug/</span><span class="regexp">/lib/</span>x86_64-linux-gnu/libm-<span class="number">2.31</span>.so...</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/lib/</span>x86_64-linux-gnu/libexpat.so.<span class="number">1</span>...</span><br><span class="line">(No debugging symbols found in <span class="regexp">/lib/</span>x86_64-linux-gnu/libexpat.so.<span class="number">1</span>)</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/lib/</span>x86_64-linux-gnu/libz.so.<span class="number">1</span>...</span><br><span class="line">(No debugging symbols found in <span class="regexp">/lib/</span>x86_64-linux-gnu/libz.so.<span class="number">1</span>)</span><br><span class="line">Reading symbols <span class="keyword">from</span> <span class="regexp">/lib64/</span>ld-linux-x86-<span class="number">64</span>.so.<span class="number">2</span>...</span><br><span class="line">(No debugging symbols found in <span class="regexp">/lib64/</span>ld-linux-x86-<span class="number">64</span>.so.<span class="number">2</span>)</span><br><span class="line"><span class="number">0</span>x00007fec057c10da in __GI___select (nfds=nfds@entry=<span class="number">0</span>, readfds=readfds@entry=<span class="number">0</span>x0, writefds=writefds@entry=<span class="number">0</span>x0, exceptfds=exceptfds@entry=<span class="number">0</span>x0, timeout=timeout@entry=<span class="number">0</span>x7fff99ce33a0) at ..<span class="regexp">/sysdeps/u</span>nix<span class="regexp">/sysv/</span>linux/select.c:<span class="number">41</span></span><br><span class="line"><span class="number">41</span>	..<span class="regexp">/sysdeps/u</span>nix<span class="regexp">/sysv/</span>linux/select.c: No such <span class="keyword">file</span> or directory.</span><br></pre></td></tr></table></figure>

<h3 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h3><p>gdb调试Python没有pdb那么方便，主要是没法直接给python代码打断点，断点都是打在解释器代码中的。<br>所以，定位到脚本对应位置比较麻烦，需要一点耐心。</p>
<figure class="highlight vim"><table><tr><td class="code"><pre><span class="line">(gdb) <span class="keyword">py</span>-<span class="keyword">list</span></span><br><span class="line">   <span class="number">1</span>    import time</span><br><span class="line">   <span class="number">2</span>    def fib(n):</span><br><span class="line">  &gt;<span class="number">3</span>        time.<span class="keyword">sleep</span>(<span class="number">0.01</span>)</span><br><span class="line">   <span class="number">4</span>        <span class="keyword">if</span> n == <span class="number">1</span> <span class="built_in">or</span> n == <span class="number">0</span>:</span><br><span class="line">   <span class="number">5</span>            <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">   <span class="number">6</span>        <span class="keyword">for</span> i in <span class="built_in">range</span>(n):</span><br><span class="line">   <span class="number">7</span>            <span class="keyword">return</span> fib(n-<span class="number">1</span>) + fib(n-<span class="number">2</span>)</span><br><span class="line">   <span class="number">8</span></span><br><span class="line">(gdb) n</span><br><span class="line"><span class="number">4970</span>	in ../Python/ceval.<span class="keyword">c</span></span><br><span class="line">(gdb) <span class="keyword">py</span>-locals</span><br><span class="line">n = <span class="number">4</span></span><br><span class="line">(gdb) <span class="keyword">b</span></span><br><span class="line">Breakpoint <span class="number">2</span> at <span class="number">0</span>x56acbe: <span class="keyword">file</span> ../Include/object.h, <span class="built_in">line</span> <span class="number">459</span>.</span><br><span class="line">(gdb) <span class="keyword">c</span></span><br><span class="line">Continuing.</span><br><span class="line"></span><br><span class="line">Breakpoint <span class="number">2</span>, _PyEval_EvalFrameDefault (<span class="keyword">f</span>=&lt;optimized out&gt;, throwflag=&lt;optimized out&gt;) at ../Include/object.h:<span class="number">459</span></span><br><span class="line"><span class="number">459</span>	in ../Include/object.h</span><br><span class="line">... # 省略一些<span class="keyword">c</span>命令</span><br><span class="line">(gdb) <span class="keyword">py</span>-locals</span><br><span class="line">n = <span class="number">3</span></span><br><span class="line">(gdb) <span class="keyword">py</span>-bt</span><br><span class="line">Traceback (most recent <span class="keyword">call</span> <span class="keyword">first</span>):</span><br><span class="line">  File <span class="string">&quot;fib.py&quot;</span>, <span class="built_in">line</span> <span class="number">4</span>, in fib</span><br><span class="line">    <span class="keyword">if</span> n == <span class="number">1</span> <span class="built_in">or</span> n == <span class="number">0</span>:</span><br><span class="line">  File <span class="string">&quot;fib.py&quot;</span>, <span class="built_in">line</span> <span class="number">7</span>, in fib</span><br><span class="line">    <span class="keyword">return</span> fib(n-<span class="number">1</span>) + fib(n-<span class="number">2</span>)</span><br><span class="line">   ... 省略一些输出</span><br><span class="line">(gdb)</span><br><span class="line">(gdb) <span class="keyword">py</span>-<span class="keyword">up</span></span><br><span class="line">#<span class="number">6</span> Frame <span class="number">0</span>x7fec0531a580, <span class="keyword">for</span> <span class="keyword">file</span> fib.<span class="keyword">py</span>, <span class="built_in">line</span> <span class="number">7</span>, in fib (n=<span class="number">4</span>, i=<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> fib(n-<span class="number">1</span>) + fib(n-<span class="number">2</span>)</span><br><span class="line">(gdb) <span class="keyword">py</span>-locals</span><br><span class="line">n = <span class="number">4</span></span><br><span class="line">i = <span class="number">0</span></span><br><span class="line">(gdb) <span class="keyword">py</span>-<span class="keyword">up</span></span><br><span class="line">#<span class="number">18</span> Frame <span class="number">0</span>x7fec0531c040, <span class="keyword">for</span> <span class="keyword">file</span> fib.<span class="keyword">py</span>, <span class="built_in">line</span> <span class="number">7</span>, in fib (n=<span class="number">7</span>, i=<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> fib(n-<span class="number">1</span>) + fib(n-<span class="number">2</span>)</span><br><span class="line">(gdb) <span class="keyword">py</span>-<span class="keyword">print</span> i</span><br><span class="line">local <span class="string">&#x27;i&#x27;</span> = <span class="number">0</span></span><br><span class="line">(gdb) <span class="keyword">py</span>-<span class="keyword">print</span> n</span><br><span class="line">local <span class="string">&#x27;n&#x27;</span> = <span class="number">7</span></span><br><span class="line">(gdb) <span class="keyword">py</span>-down</span><br><span class="line">#<span class="number">12</span> Frame <span class="number">0</span>x7fec0531a900, <span class="keyword">for</span> <span class="keyword">file</span> fib.<span class="keyword">py</span>, <span class="built_in">line</span> <span class="number">7</span>, in fib (n=<span class="number">6</span>, i=<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> fib(n-<span class="number">1</span>) + fib(n-<span class="number">2</span>)</span><br><span class="line">(gdb) <span class="keyword">py</span>-<span class="keyword">print</span> n</span><br><span class="line">local <span class="string">&#x27;n&#x27;</span> = <span class="number">6</span></span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://wiki.python.org/moin/DebuggingWithGdb">https://wiki.python.org/moin/DebuggingWithGdb</a></li>
<li><a href="https://www.geeksforgeeks.org/gdb-step-by-step-introduction/">https://www.geeksforgeeks.org/gdb-step-by-step-introduction/</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python装饰器详解</title>
    <url>/2019/09/23/decorator-in-python/</url>
    <content><![CDATA[<p>装饰器应该是Python最富有表现力的语法结构之一了，基于装饰器很多功能可以实现得比较优雅。<br>Python中的装饰器，来源于设计模式中的装饰器模式。顾名思义，所谓装饰器就是对原有的对象做一些装饰，也就是给已有的对象添加一些功能。</p>
<h2 id="简易装饰器"><a href="#简易装饰器" class="headerlink" title="简易装饰器"></a>简易装饰器</h2><p>装饰器本质上是函数替换. 装饰器被调用会返回一个函数, 被装饰函数会被返回的这个函数替换.<br>要使用装饰器，先得定义一个装饰器函数，然后在需要装饰的函数的前一行使用<code>@</code>符号加上装饰器名称。<br>下面是一个简单是例子, <code>hello</code>函数被<code>running</code>装饰器装饰, <code>running</code>返回了<code>fuck</code>函数, 此时调用<code>hello</code>就变成了调用<code>fuck</code>, 实现了函数功能的改变.</p>
<span id="more"></span>

<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">fuck</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;fuck, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">function</span>):</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;replace %r to %r&#x27;</span> % (function, fuck))</span><br><span class="line">    <span class="keyword">return</span> fuck</span><br><span class="line"></span><br><span class="line"><span class="meta">@running</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;--- before call hello ---&#x27;</span>)</span><br><span class="line"><span class="built_in">print</span>(hello())</span><br><span class="line"><span class="built_in">print</span>(hello(<span class="string">&#x27;foo&#x27;</span>))</span><br></pre></td></tr></table></figure>

<p>output</p>
<figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">replace &lt;<span class="keyword">function</span> <span class="title">hello</span> at 0x1052e98c8&gt; to &lt;<span class="keyword">function</span> <span class="title">fuck</span> at 0x104d15f28&gt;</span><br><span class="line"><span class="comment">--- before call hello ---</span></span><br><span class="line">fuck, nobody!</span><br><span class="line">fuck, foo!</span><br></pre></td></tr></table></figure>

<p>装饰器在这里的效果等效于函数嵌套，不过看起来有点别扭。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">fuck</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;fuck, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">function</span>):</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;replace %r to %r&#x27;</span> % (function, fuck))</span><br><span class="line">    <span class="keyword">return</span> fuck</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">hello = running(hello)</span><br><span class="line"><span class="built_in">print</span>(hello())</span><br><span class="line"><span class="built_in">print</span>(hello(<span class="string">&#x27;foo&#x27;</span>))</span><br></pre></td></tr></table></figure>

<p><strong>注意：一旦通过<code>@running</code>装饰了函数，不管被装饰函数是否运行，python解释器都会执行一遍running函数。</strong></p>
<h2 id="普通装饰器"><a href="#普通装饰器" class="headerlink" title="普通装饰器"></a>普通装饰器</h2><p>前面这个装饰器将hello函数替换成fuck函数之外就没有别的功能了, 只是为了演示装饰器的原理, 并没有什么实际用处</p>
<p>现在我们写一个计时器装饰器.<br>还是将hello函数替换成fuck, 这里我们将fuck函数的定义移动到runing内部, fuck函数在调用hello的同时, 实现计时功能.<br>这样有一个好处, 这个fuck函数就不是全局可见的, 不会污染全局环境, 还可以用到闭包的一些特性.</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">func</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">fuck</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        start = time.time()</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;`%s` is running...&#x27;</span> % func.__name__)</span><br><span class="line">        _result = func(*args, **kwargs)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;run `%s` takes %s seconds&#x27;</span> % (func.__name__, time.time()-start))</span><br><span class="line">        <span class="keyword">return</span> _result</span><br><span class="line">    <span class="keyword">return</span> fuck</span><br><span class="line"></span><br><span class="line"><span class="meta">@running</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(hello(<span class="string">&#x27;foo&#x27;</span>))</span><br><span class="line"><span class="built_in">print</span>(hello(<span class="string">&#x27;bar&#x27;</span>))</span><br><span class="line"><span class="built_in">print</span>(hello.__name__)</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight autohotkey"><table><tr><td class="code"><pre><span class="line">`hello` is running...</span><br><span class="line">run `hello` takes <span class="number">3.981590270996094</span>e-<span class="number">05</span> seconds</span><br><span class="line"><span class="built_in">hello,</span> foo!</span><br><span class="line">`hello` is running...</span><br><span class="line">run `hello` takes <span class="number">3.814697265625</span>e-<span class="number">06</span> seconds</span><br><span class="line"><span class="built_in">hello,</span> bar!</span><br><span class="line">fuck</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h2 id="消除装饰器的副作用"><a href="#消除装饰器的副作用" class="headerlink" title="消除装饰器的副作用"></a>消除装饰器的副作用</h2><p>上一个装饰器也有一个问题，因为装饰器本质上是函数替换. 就是经过装饰的函数一些属性变了, 比如<code>hello.__name__</code>变成了<code>fuck</code>。</p>
<p>所以我们要将这些属性复制到新函数上, 同时由于fuck函数已经没有fuck功能了, 我们将它重命名为wrapper</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">func</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        start = time.time()</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;`%s` is running...&#x27;</span> % func.__name__)</span><br><span class="line">        _result = func(*args, **kwargs)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;run `%s` takes %s seconds&#x27;</span> % (func.__name__, time.time()-start))</span><br><span class="line">        <span class="keyword">return</span> _result</span><br><span class="line"></span><br><span class="line">    wrapper.__name__ = func.__name__</span><br><span class="line">    <span class="keyword">return</span> wrapper</span><br><span class="line"></span><br><span class="line"><span class="meta">@running</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(hello.__name__)</span><br></pre></td></tr></table></figure>

<p>output</p>
<figure class="highlight ebnf"><table><tr><td class="code"><pre><span class="line"><span class="attribute">hello</span></span><br></pre></td></tr></table></figure>

<p>这个问题也可以通过python内置的<code>functools.wraps</code>装饰器解决，这个装饰器对原函数的一些属性进行了复制。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> functools</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">func</span>):</span><br><span class="line"><span class="meta">    @functools.wraps(<span class="params">func</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        start = time.time()</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;`%s` is running...&#x27;</span> % func.__name__)</span><br><span class="line">        _result = func(*args, **kwargs)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;run `%s` takes %s seconds&#x27;</span> % (func.__name__, time.time()-start))</span><br><span class="line">        <span class="keyword">return</span> _result</span><br><span class="line">    <span class="keyword">return</span> wrapper</span><br></pre></td></tr></table></figure>

<h2 id="带参数的装饰器"><a href="#带参数的装饰器" class="headerlink" title="带参数的装饰器"></a>带参数的装饰器</h2><p>上一个装饰器还有一个缺点是, 装饰器不能接受参数, 现在我们来实现带参数的装饰器</p>
<p>带参数的装饰器其实就是多嵌套一层函数</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> functools</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">system</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">decorator</span>(<span class="params">func</span>):</span><br><span class="line"><span class="meta">        @functools.wraps(<span class="params">func</span>)</span></span><br><span class="line">        <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">            start = time.time()</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;`%s` is running at %s&#x27;</span> % (func.__name__, system))</span><br><span class="line">            _result = func(*args, **kwargs)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;run `%s` takes %s seconds&#x27;</span> % (func.__name__, time.time()-start))</span><br><span class="line">            <span class="keyword">return</span> _result</span><br><span class="line">        <span class="keyword">return</span> wrapper</span><br><span class="line">    <span class="keyword">return</span> decorator</span><br><span class="line"></span><br><span class="line"><span class="meta">@running(<span class="params"><span class="string">&#x27;mac&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(hello(<span class="string">&#x27;foo&#x27;</span>))</span><br></pre></td></tr></table></figure>

<p>output</p>
<figure class="highlight autohotkey"><table><tr><td class="code"><pre><span class="line">`hello` is running at mac</span><br><span class="line">run `hello` takes <span class="number">3.910064697265625</span>e-<span class="number">05</span> seconds</span><br><span class="line"><span class="built_in">hello,</span> foo!</span><br></pre></td></tr></table></figure>

<h2 id="可选带参数的装饰器"><a href="#可选带参数的装饰器" class="headerlink" title="可选带参数的装饰器"></a>可选带参数的装饰器</h2><p>有时我们希望装饰器更为通用, 适用于带参数和不带参数的场景</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> functools</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">*running_args, system=<span class="string">&#x27;mac&#x27;</span>, **running_kwargs</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">decorator</span>(<span class="params">func</span>):</span><br><span class="line"><span class="meta">        @functools.wraps(<span class="params">func</span>)</span></span><br><span class="line">        <span class="keyword">def</span> <span class="title function_">wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">            start = time.time()</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;`%s` is running at %s&#x27;</span> % (func.__name__, system))</span><br><span class="line">            _result = func(*args, **kwargs)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;run `%s` takes %s seconds&#x27;</span> % (func.__name__, time.time()-start))</span><br><span class="line">            <span class="keyword">return</span> _result</span><br><span class="line">        <span class="keyword">return</span> wrapper</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(running_args) == <span class="number">1</span> <span class="keyword">and</span> <span class="built_in">callable</span>(running_args[<span class="number">0</span>]):</span><br><span class="line">        <span class="keyword">return</span> decorator(running_args[<span class="number">0</span>])</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> decorator</span><br><span class="line"></span><br><span class="line"><span class="meta">@running(<span class="params">system=<span class="string">&#x27;linux&#x27;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"><span class="meta">@running</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello2</span>(<span class="params">who=<span class="string">&#x27;nobody&#x27;</span></span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello, %s!&quot;</span> % who</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(hello(<span class="string">&#x27;foo&#x27;</span>))</span><br><span class="line"><span class="built_in">print</span>(hello2(<span class="string">&#x27;bar&#x27;</span>))</span><br></pre></td></tr></table></figure>

<p>output</p>
<figure class="highlight autohotkey"><table><tr><td class="code"><pre><span class="line">`hello` is running at linux</span><br><span class="line">run `hello` takes <span class="number">2.5033950805664062</span>e-<span class="number">05</span> seconds</span><br><span class="line"><span class="built_in">hello,</span> foo!</span><br><span class="line">`hello2` is running at mac</span><br><span class="line">run `hello2` takes <span class="number">3.814697265625</span>e-<span class="number">06</span> seconds</span><br><span class="line"><span class="built_in">hello,</span> bar!</span><br></pre></td></tr></table></figure>


]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>装饰器</tag>
      </tags>
  </entry>
  <entry>
    <title>使用PlantUML做软件设计</title>
    <url>/2019/09/21/design-program-with-plantuml/</url>
    <content><![CDATA[<h2 id="Plantuml-是什么？"><a href="#Plantuml-是什么？" class="headerlink" title="Plantuml 是什么？"></a>Plantuml 是什么？</h2><p>首先得说一下UML，<strong>统一建模语言</strong>（英语：Unified Modeling Language，缩写 UML）是非专利的第三代和规约语言。</p>
<p>UML涵盖了很多类型的图，主要都是应用于软件开发行业。</p>
<p>在UML系统开发中有三个主要的模型：</p>
<ul>
<li><p><strong>功能模型</strong>：从用户的角度展示系统的功能，包括用例图。</p>
</li>
<li><p><strong>对象模型</strong>：采用对象，属性，操作，关联等概念展示系统的结构和基础，包括类别图、对象图。</p>
</li>
<li><p><strong>动态模型</strong>：展现系统的内部行为。包括序列图，活动图，状态图。</p>
</li>
</ul>
<p>而PlantUML是一个开源项目，除了支持快速绘制上面这些类型的图表之外，还支持很多图表，具体查看<a href="http://plantuml.com/zh/index">官方网站</a>。</p>
<p>PlantUML通过编写文本的方式来定义UML图表，有点类似markdown，然后生成图表</p>
<span id="more"></span>

<p>示例：</p>
<figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line">@startuml</span><br><span class="line">&#x27; Split <span class="keyword">into</span> <span class="number">4</span> pages</span><br><span class="line">page <span class="number">2</span>x2</span><br><span class="line">skinparam pageMargin <span class="number">10</span></span><br><span class="line">skinparam pageExternalColor gray</span><br><span class="line">skinparam pageBorderColor black</span><br><span class="line"></span><br><span class="line"><span class="built_in">class</span> BaseClass</span><br><span class="line"></span><br><span class="line">namespace net.dummy <span class="comment">#DDDDDD &#123;</span></span><br><span class="line">	.BaseClass &lt;|<span class="comment">-- Person</span></span><br><span class="line">	Meeting o<span class="comment">-- Person</span></span><br><span class="line"></span><br><span class="line">	.BaseClass &lt;|- Meeting</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">namespace net.foo &#123;</span><br><span class="line">  net.dummy.Person  &lt;|- Person</span><br><span class="line">  .BaseClass &lt;|<span class="comment">-- Person</span></span><br><span class="line"></span><br><span class="line">  net.dummy.Meeting o<span class="comment">-- Person</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">BaseClass &lt;|<span class="comment">-- net.unused.Person</span></span><br><span class="line">@enduml</span><br></pre></td></tr></table></figure>

<p>输出效果<br><img data-src="https://image.ponder.work/mweb/15690347198367.jpg"></p>
<h2 id="环境搭建"><a href="#环境搭建" class="headerlink" title="环境搭建"></a>环境搭建</h2><p>我们需要一个编辑器&#x2F;IDE来编辑和预览UML图表，这里使用<a href="https://code.visualstudio.com/">vscode</a></p>
<h3 id="安装PlantUML插件"><a href="#安装PlantUML插件" class="headerlink" title="安装PlantUML插件"></a>安装PlantUML插件</h3><p><img data-src="https://image.ponder.work/mweb/15690363185298.jpg" alt="-w1151"></p>
<h3 id="配置插件渲染方式"><a href="#配置插件渲染方式" class="headerlink" title="配置插件渲染方式"></a>配置插件渲染方式</h3><p>渲染方式有两种</p>
<ul>
<li>local 本地的Java渲染服务，需要Java运行环境及Graphviz</li>
<li>PlantUMLServer 远程渲染服务器</li>
</ul>
<p>方便起见，我们直接用官方的服务器<br><img data-src="https://image.ponder.work/mweb/15690367077023.jpg" alt="-w1151"></p>
<h3 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h3><ol>
<li><p>新建 test.puml</p>
 <figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="variable">@startuml</span></span><br><span class="line">&#x27; Split into <span class="number">4</span> pages</span><br><span class="line">class BaseClass</span><br><span class="line"></span><br><span class="line"><span class="variable">@enduml</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>预览图表<br><img data-src="https://image.ponder.work/mweb/15690369832230.jpg" alt="-w1151"></p>
</li>
</ol>
<h2 id="PlantUML基础语法"><a href="#PlantUML基础语法" class="headerlink" title="PlantUML基础语法"></a>PlantUML基础语法</h2><p>由于PlantUML支持的图表类型比较多，每种图表的语法还不太一致，这里只简单介绍通用的语法，具体的图表还是建议查看官方文档。</p>
<p>也建议大家，在使用过程中记录适合自己的每种图表的模板，作图效率大大提高。</p>
<p><strong>注意</strong>：语句类型是和图关联的，也就是说某些语句只能在某个图表用，而且PlantUML会根据你用到的语句猜测你是什么图表，然后进行渲染。</p>
<h3 id="图表块"><a href="#图表块" class="headerlink" title="图表块"></a>图表块</h3><p>以<code>@startuml</code>和<code>@enduml</code>标记一个图表，还可以在开始处记录图表的名称（这里是hello-world）</p>
<figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="variable">@startuml</span> hello-world</span><br><span class="line">You -&gt; <span class="attribute">World</span>: Hello</span><br><span class="line"><span class="variable">@enduml</span></span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/15690376853589.jpg" alt="-w315"></p>
<h3 id="注释和标记"><a href="#注释和标记" class="headerlink" title="注释和标记"></a>注释和标记</h3><ul>
<li>单行注释：单引号 <code>&#39;</code> 在行首</li>
<li>多行注释：以<code>/&#39;</code> 开始， 以 <code>/&#39;</code> 结束</li>
<li>图表标记：<code>note left|right|top|bottom of XXX</code></li>
</ul>
<figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="variable">@startuml</span> hello-world</span><br><span class="line">&#x27; You -&gt; <span class="attribute">World</span>: Hello 单行注释</span><br><span class="line"></span><br><span class="line">/&#x27;</span><br><span class="line">you can <span class="keyword">not</span> 多行注释</span><br><span class="line">see me</span><br><span class="line">&#x27;/</span><br><span class="line"></span><br><span class="line">You &lt;-&gt; <span class="attribute">World</span>: Hello</span><br><span class="line">note left of <span class="attribute">You</span>: yoyoyo</span><br><span class="line">&#x27; 标记</span><br><span class="line"><span class="variable">@enduml</span></span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/15690390104495.jpg" alt="-w254"></p>
<h3 id="对象和关系"><a href="#对象和关系" class="headerlink" title="对象和关系"></a>对象和关系</h3><p><strong>对象</strong>： 图表中的方块，一般出现才关系的两端，有些需要额外定义（如接口，类）<br><strong>关系</strong>：一般用形如箭头的字符来表示对象之间的关系，字符不同样式不同</p>
<figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="variable">@startuml</span> hello-world1</span><br><span class="line">You -&gt; <span class="attribute">World</span>: Hello</span><br><span class="line">You &lt;- <span class="attribute">World</span>: also Hello</span><br><span class="line">You &lt;-&gt; <span class="attribute">World</span>: Hololo</span><br><span class="line">You &lt;--&gt; <span class="attribute">World</span>: Hololo</span><br><span class="line"><span class="variable">@enduml</span></span><br></pre></td></tr></table></figure>
<p><img data-src="https://image.ponder.work/mweb/15690396911033.jpg" alt="-w260"></p>
<h2 id="一些例子"><a href="#一些例子" class="headerlink" title="一些例子"></a>一些例子</h2><h3 id="时序图"><a href="#时序图" class="headerlink" title="时序图"></a>时序图</h3><figure class="highlight coq"><table><tr><td class="code"><pre><span class="line">@startuml</span><br><span class="line">participant 用户</span><br><span class="line">用户 -&gt; 店长: 询问：iphone11 有吗？</span><br><span class="line">activate 店长</span><br><span class="line"></span><br><span class="line">note <span class="built_in">left</span> of 用户: 我很暴躁</span><br><span class="line">note <span class="built_in">left</span> of 店长: 巧了，我也是</span><br><span class="line"></span><br><span class="line">店长 -&gt; 服务员: 命令：去查下库存</span><br><span class="line">activate 服务员</span><br><span class="line">服务员 -&gt; 库存系统: 查询库存</span><br><span class="line">activate 库存系统</span><br><span class="line"><span class="keyword">return</span> 库存量</span><br><span class="line">deactivate 库存系统</span><br><span class="line">alt <span class="string">&quot;库存量 &gt; 0&quot;</span></span><br><span class="line">	服务员 -&gt; 店长: 通知：货多得很</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">	服务员 -&gt; 店长: 通知：没货了</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line">deactivate 服务员</span><br><span class="line"></span><br><span class="line">alt <span class="string">&quot;库存量 &lt;= 0&quot;</span></span><br><span class="line">	店长 -&gt; 用户: 不凑巧，买完了</span><br><span class="line">    用户 -&gt; 店长: 没货还挂着商品，耍人吗???</span><br><span class="line">    用户 -&gt; 店长: 愉快地打了一架</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line">店长 -&gt; 用户: Done</span><br><span class="line">@enduml</span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/15690411434468.jpg" alt="-w765"></p>
<h3 id="类图"><a href="#类图" class="headerlink" title="类图"></a>类图</h3><figure class="highlight perl"><table><tr><td class="code"><pre><span class="line"><span class="variable">@startuml</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Head</span></span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">Body</span></span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    head</span><br><span class="line">    body</span><br><span class="line">    eye_num = <span class="number">2</span></span><br><span class="line">    hand_num = <span class="number">0</span></span><br><span class="line">    leg_num = <span class="number">4</span></span><br><span class="line">    tail_num = <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    run()</span><br><span class="line">    shout()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Cat</span></span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">Fish</span> </span>&#123;</span><br><span class="line">    hand_num = <span class="number">0</span></span><br><span class="line">    leg_num = <span class="number">0</span></span><br><span class="line">    tail_num = <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    swim()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FishShoal</span> </span>&#123;</span><br><span class="line">    shoal_size</span><br><span class="line">    fish_type</span><br><span class="line">    fish_array</span><br><span class="line"></span><br><span class="line">    migrate()</span><br><span class="line">&#125;</span><br><span class="line">note left of FishShoal: 鱼群</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Human</span> </span>&#123;</span><br><span class="line">    hand_num = <span class="number">2</span></span><br><span class="line">    leg_num = <span class="number">2</span></span><br><span class="line">    tail_num = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    speak()</span><br><span class="line">    work()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Sutdent</span> </span>&#123;</span><br><span class="line">    <span class="keyword">study</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">School</span> </span>&#123;</span><br><span class="line">    location</span><br><span class="line">    level</span><br><span class="line">    student_num</span><br><span class="line">    student_array</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="string">&#x27; 组合</span></span><br><span class="line"><span class="string">Animal *-- Head</span></span><br><span class="line"><span class="string">Animal *-- Body</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&#x27;</span> 继承、扩展</span><br><span class="line">Animal &lt;|-- Cat</span><br><span class="line">Animal &lt;|-- Fish</span><br><span class="line">Animal &lt;|-- Human</span><br><span class="line">Human &lt;|-- Sutdent</span><br><span class="line"></span><br><span class="line"><span class="string">&#x27; 聚合</span></span><br><span class="line"><span class="string">School o-- Sutdent: contains &gt;</span></span><br><span class="line"><span class="string">FishShoal o-- Fish: contains &gt;</span></span><br><span class="line"><span class="string">@enduml</span></span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/15690445125047.jpg" alt="-w1253"></p>
<h2 id="参考资源"><a href="#参考资源" class="headerlink" title="参考资源"></a>参考资源</h2><ul>
<li><a href="https://real-world-plantuml.com/">https://real-world-plantuml.com/</a> PlantUML示例网站</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>UML</tag>
        <tag>软件架构</tag>
      </tags>
  </entry>
  <entry>
    <title>开发Python库并发布到PyPI指南</title>
    <url>/2020/09/10/develop-python-library-and-upload-it-to-pypi-guide/</url>
    <content><![CDATA[<h2 id="PyPI"><a href="#PyPI" class="headerlink" title="PyPI"></a>PyPI</h2><p><a href="https://pypi.org/">PyPI</a>（英语：Python Package Index，简称PyPI）是一个用于存储Python写成的软件包的软件存储库，我们平时用pip安装的库就是来自于PyPI</p>
<p>而且，通过PyPI我们可以把自己写的库代码分享给别人，这也是开源的乐趣之一。</p>
<span id="more"></span>

<h2 id="用到的库代码"><a href="#用到的库代码" class="headerlink" title="用到的库代码"></a>用到的库代码</h2><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">├── MANIFEST<span class="selector-class">.in</span></span><br><span class="line">├── README<span class="selector-class">.md</span></span><br><span class="line">├── dingtalk_log_handler</span><br><span class="line">│   ├── __init__<span class="selector-class">.py</span></span><br><span class="line">├── setup.py</span><br></pre></td></tr></table></figure>
<p>整体代码结构，其中<a href="https://github.com/ruanimal/dingtalk-log-handler">dingtalk_log_handler</a>是我写的一个用于发日志到钉钉群的库，也是这次要发布的库。</p>
<p>先看下库的主体代码<code>dingtalk_log_handler/__init__.py</code>, 由于功能比较简单，逻辑就都写在<code>__init__.py</code>里了</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"><span class="comment"># 其他import略去</span></span><br><span class="line"></span><br><span class="line">__author__ = <span class="string">&#x27;ruan.lj&#x27;</span></span><br><span class="line">__version__ = <span class="string">&#x27;0.0.2&#x27;</span></span><br><span class="line">__all__ = (</span><br><span class="line">    <span class="string">&#x27;OAPI_DOMAIN&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;DingTalkHandler&#x27;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">OAPI_DOMAIN = <span class="string">&#x27;oapi.dingtalk.com&#x27;</span>   <span class="comment"># dingtalk open api domain</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DingdingApiError</span>(<span class="title class_ inherited__">RuntimeError</span>):</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DingTalkHandler</span>(logging.Handler):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Handler for logging message to dingtalk&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line">    <span class="comment"># 略去逻辑代码</span></span><br></pre></td></tr></table></figure>

<h2 id="编写-setup-py"><a href="#编写-setup-py" class="headerlink" title="编写 setup.py"></a>编写 setup.py</h2><p>setup.py指引了打包工具如何打包我们的库，功能与类似Makefile</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> setuptools <span class="keyword">import</span> setup, find_packages</span><br><span class="line"><span class="keyword">from</span> dingtalk_log_handler <span class="keyword">import</span> __author__, __version__</span><br><span class="line"></span><br><span class="line"><span class="comment"># read the contents of your README file</span></span><br><span class="line"><span class="keyword">from</span> os <span class="keyword">import</span> path</span><br><span class="line">this_directory = path.abspath(path.dirname(__file__))</span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(path.join(this_directory, <span class="string">&#x27;README.md&#x27;</span>), encoding=<span class="string">&#x27;utf-8&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    long_description = f.read()</span><br><span class="line"></span><br><span class="line">setup(</span><br><span class="line">    name=<span class="string">&#x27;dingtalk-log-handler&#x27;</span>,</span><br><span class="line">    version=__version__,</span><br><span class="line">    author=__author__,</span><br><span class="line">    author_email=<span class="string">&#x27;xxx@foxmail.com&#x27;</span>,</span><br><span class="line">    description=<span class="string">&#x27;log handler for send message to dingtalk&#x27;</span>,</span><br><span class="line">    long_description=long_description,</span><br><span class="line">    long_description_content_type=<span class="string">&#x27;text/markdown&#x27;</span>,</span><br><span class="line">    classifiers=[</span><br><span class="line">        <span class="string">&#x27;Development Status :: 4 - Beta&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;Intended Audience :: Developers&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;Topic :: Software Development :: Libraries&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;Programming Language :: Python :: 3&#x27;</span>,</span><br><span class="line">        <span class="comment"># 省略一下</span></span><br><span class="line">    ],</span><br><span class="line">    packages=find_packages(),</span><br><span class="line">    python_requires=<span class="string">&#x27;&gt;=3.5&#x27;</span>,</span><br><span class="line">    install_requires=[],</span><br><span class="line">    project_urls=&#123;</span><br><span class="line">        <span class="string">&#x27;Source&#x27;</span>: <span class="string">&#x27;https://github.com/ruanimal/dingtalk-log-handler&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<p>具体打包的功能由<code>setuptools.setup</code>函数实现，我们只需要修改对应的参数即可</p>
<p>这些参数影响打包的行为，以及在PyPI页面上显示的信息</p>
<p>主要的参数说明，详细信息参考<a href="https://packaging.python.org/guides/distributing-packages-using-setuptools">文档</a></p>
<ul>
<li>name: 库名，也就是<code>pip install xxx</code>时的名称</li>
<li>version: 版本，我们这里是复用了库代码里的版本号</li>
<li>author: 作者，同上</li>
<li>author_email: 作者邮箱</li>
<li>description: 库说明，在<code>pip search xxx</code>的时候可以看到</li>
<li>long_description: 库详细说明，显示在PyPI完整上，我们这里从<code>README.md</code>文件中读取具体内容</li>
<li>long_description: 库详细说明的格式，这里使用markdown</li>
<li>classifiers: 库的类别信息，<a href="https://pypi.org/classifiers/">所有可选值参考</a></li>
<li>packages: 库包含的python包，通过find_packages自动添加</li>
<li>py_modules: 库包含的顶级Python模块，我们这里没有</li>
<li>python_requires: 支持的Python版本</li>
<li>install_requires: 依赖的其他库，格式与<code>pip freeze</code>输出的格式相同</li>
<li>project_urls: 项目的一些链接信息，<a href="https://packaging.python.org/guides/distributing-packages-using-setuptools/#project-urls">可选值列表</a></li>
</ul>
<h2 id="编写-MANIFEST-in"><a href="#编写-MANIFEST-in" class="headerlink" title="编写 MANIFEST.in"></a>编写 MANIFEST.in</h2><p>打包时默认只会包含包代码和一些必要的文件，见<a href="https://packaging.python.org/guides/using-manifest-in/#how-files-are-included-in-an-sdist">列表</a><br>如果要包含其他资源文件，必须编写<code>MANIFEST.in</code>来说明说明</p>
<p>MANIFEST.in</p>
<figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">include</span> README.md</span><br><span class="line"><span class="keyword">recursive</span>-<span class="keyword">include</span> dingtalk_log_handler *</span><br><span class="line"><span class="keyword">global</span>-<span class="keyword">exclude</span> __pycache__</span><br><span class="line"><span class="keyword">global</span>-<span class="keyword">exclude</span> tmp</span><br><span class="line"><span class="keyword">global</span>-<span class="keyword">exclude</span> *.py[co]</span><br></pre></td></tr></table></figure>

<p>语法和shell的通配符语法类似</p>
<ul>
<li><code>include &lt;pattern&gt; &lt;pattern2&gt; ...</code>: 包含项目根目录匹配通配符的文件</li>
<li><code>recursive-include &lt;dir-pattern&gt; &lt;pattern&gt; &lt;pattern2&gt; ...</code>: 递归地包含指定目录匹配通配符的文件</li>
<li><code>global-exclude &lt;pattern&gt; &lt;pattern2&gt; ...</code>: 递归地排除匹配通配符的文件</li>
</ul>
<h2 id="打包"><a href="#打包" class="headerlink" title="打包"></a>打包</h2><p>执行: <code>pip3 install wheel &amp;&amp; python3 setup.py sdist bdist_wheel</code></p>
<p>输出文件</p>
<figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">dist</span><br><span class="line">├── dingtalk-<span class="keyword">log</span>-<span class="keyword">handler</span><span class="number">-0.0</span><span class="number">.2</span>.tar.gz</span><br><span class="line">└── dingtalk_log_handler<span class="number">-0.0</span><span class="number">.2</span>-py3-<span class="keyword">none</span>-<span class="keyword">any</span>.whl</span><br></pre></td></tr></table></figure>

<h2 id="上传"><a href="#上传" class="headerlink" title="上传"></a>上传</h2><p>我们可以通过<a href="https://test.pypi.org/">PyPI的测试站点</a> 来练习库文件的上传，并测试效果</p>
<ol>
<li><a href="https://test.pypi.org/account/register/">注册账户</a></li>
<li>安装上传工具：<code>pip install twine -U</code></li>
<li>上传文件：<code>twine upload --repository testpypi dist/*</code></li>
</ol>
<p>熟悉流程之后，就可以换成<a href="https://pypi.org/">正式站点</a>，指定正式仓库(–repository pypi)上传文件</p>
<p>这样全世界的人都能看到你的库了。</p>
<h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><p>可以在<a href="https://pypi.org/project/dingtalk-log-handler/">这里</a>找到我这次上传的库</p>
<p>可以看到 setup.py 文件里的很多信息会对应体现在PyPI网页上<br><img data-src="https://image.ponder.work/mweb/2020-09-11-15997969319299.jpg"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://packaging.python.org/guides/distributing-packages-using-setuptools">https://packaging.python.org/guides/distributing-packages-using-setuptools</a></li>
<li><a href="https://packaging.python.org/guides/using-manifest-in/">https://packaging.python.org/guides/using-manifest-in/</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>PyPI</tag>
      </tags>
  </entry>
  <entry>
    <title>Python2 与 Python3 区别</title>
    <url>/2017/08/30/difference-between-python2-and-python3/</url>
    <content><![CDATA[<h2 id="速查表"><a href="#速查表" class="headerlink" title="速查表"></a>速查表</h2><p>Python2 vs Python3</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Python2</th>
<th>Python3</th>
<th>Addtion Info</th>
</tr>
</thead>
<tbody><tr>
<td>try</td>
<td>try except ValueError, e</td>
<td>try except ValueError as e</td>
<td></td>
</tr>
<tr>
<td>exception</td>
<td>ValueError(‘aa’).message</td>
<td>-</td>
<td>python3中可用ValueError(‘aa’).args[0] 替代</td>
</tr>
<tr>
<td><code>__import__</code></td>
<td><code>__import__</code></td>
<td><code>__import__</code></td>
<td>推荐importlib.import_module替代，可移植性更好</td>
</tr>
<tr>
<td>print</td>
<td>关键字</td>
<td>函数</td>
<td></td>
</tr>
<tr>
<td>unicode</td>
<td>unicode</td>
<td>str</td>
<td>python2默认的string是bytes， Python3中是unicode</td>
</tr>
<tr>
<td>bytes</td>
<td>str</td>
<td>bytes</td>
<td></td>
</tr>
<tr>
<td>division</td>
<td>1 &#x2F; 2</td>
<td>1 &#x2F;&#x2F; 2</td>
<td></td>
</tr>
<tr>
<td>division</td>
<td>1 &#x2F; 2.0</td>
<td>1 &#x2F; 2</td>
<td></td>
</tr>
<tr>
<td>不等于操作符</td>
<td>&lt;&gt;</td>
<td>!&#x3D;</td>
<td>这个操作费在Python3中已废弃，使用!&#x3D;替代</td>
</tr>
<tr>
<td>round</td>
<td>round(0.5) &#x3D;&#x3D; 1.0</td>
<td>round(0.5) &#x3D;&#x3D; 0</td>
<td>Python3内建的 round 是四舍六入五成双的机制</td>
</tr>
<tr>
<td>xrange</td>
<td>xrange</td>
<td>range</td>
<td></td>
</tr>
<tr>
<td>range</td>
<td>range(1,2)</td>
<td>list(range(1,2))</td>
<td></td>
</tr>
<tr>
<td>reduce</td>
<td>reduce</td>
<td>-</td>
<td>Python使用functools.reduce替代</td>
</tr>
<tr>
<td>dict.keys</td>
<td>dict.keys()</td>
<td>list(dict.keys())</td>
<td>python的dict遍历不保证顺序, 同一个字典py2和py3的遍历顺序可能不一样</td>
</tr>
<tr>
<td>dict.iterkeys</td>
<td>dict.iterkeys()</td>
<td>dict.keys()</td>
<td></td>
</tr>
<tr>
<td>dict.items</td>
<td>dict.items()</td>
<td>list(dict.items())</td>
<td></td>
</tr>
<tr>
<td>dict.iteritems</td>
<td>dict.iteritems()</td>
<td>dict.items()</td>
<td></td>
</tr>
<tr>
<td>内置库</td>
<td>commands</td>
<td>-</td>
<td>用subprocess替代</td>
</tr>
<tr>
<td>内置库</td>
<td>sys.setdefaultencoding</td>
<td>-</td>
<td></td>
</tr>
<tr>
<td>内置库</td>
<td>Queue</td>
<td>queue</td>
<td></td>
</tr>
<tr>
<td>内置库</td>
<td>ConfigParser</td>
<td>configparser</td>
<td></td>
</tr>
</tbody></table>
<h2 id="详细对比"><a href="#详细对比" class="headerlink" title="详细对比"></a>详细对比</h2><h3 id="unicode"><a href="#unicode" class="headerlink" title="unicode"></a>unicode</h3><p>Python2</p>
<ul>
<li>字符串分 <code>str(&#39;&#39;)</code> 和 <code>unicode(u&#39;&#39;)</code></li>
<li>str，就是以<code>&#39;xxx&#39;</code>形式输入的字符，实际储存的值是<code>xxx</code>经过系统默认字符集encode过的字节串（bytes），如’\xe8\x86\x9c’</li>
<li>unicode，就是以<code>u&#39;xxx&#39;</code>形式输入的字符，实际储存的值是<code>xxx</code>对应的unicode码， 如<code>u&#39;\u819c&#39;</code></li>
<li>str，其实等于python3中的字节串（bytes）</li>
<li>unicode，其实等于python3中的字符串（str）</li>
<li>在python2中unicode才是真正的字符串</li>
</ul>
<span id="more"></span>

<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="string">&#x27;膜法&#x27;</span></span><br><span class="line"><span class="string">&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="string">u&#x27;膜法&#x27;</span></span><br><span class="line"><span class="string">u&#x27;\u819c\u6cd5&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="string">&#x27;膜法&#x27;</span>.decode(<span class="string">&#x27;utf8&#x27;</span>)</span><br><span class="line"><span class="string">u&#x27;\u819c\u6cd5&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">print</span> <span class="string">u&#x27;\u819c\u6cd5&#x27;</span></span><br><span class="line">膜法</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">print</span> <span class="string">&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span></span><br><span class="line">膜法</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="string">u&#x27;膜法&#x27;</span></span><br><span class="line"><span class="string">u&#x27;\u819c\u6cd5&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(<span class="string">&#x27;膜法&#x27;</span>)</span><br><span class="line">&lt;<span class="built_in">type</span> <span class="string">&#x27;str&#x27;</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(<span class="string">u&#x27;膜法&#x27;</span>)</span><br><span class="line">&lt;<span class="built_in">type</span> <span class="string">&#x27;unicode&#x27;</span>&gt;</span><br><span class="line">&gt;&gt;&gt;</span><br></pre></td></tr></table></figure>

<p>Python3</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="string">&#x27;膜法&#x27;</span></span><br><span class="line"><span class="string">&#x27;膜法&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="string">&#x27;\u819c\u6cd5&#x27;</span></span><br><span class="line"><span class="string">&#x27;膜法&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="string">&#x27;膜法&#x27;</span>.encode(<span class="string">&#x27;utf8&#x27;</span>)</span><br><span class="line"><span class="string">b&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(<span class="string">&#x27;膜法&#x27;</span>)</span><br><span class="line">&lt;<span class="keyword">class</span> <span class="string">&#x27;str&#x27;</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(<span class="string">b&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span>)</span><br><span class="line">&lt;<span class="keyword">class</span> <span class="string">&#x27;bytes&#x27;</span>&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">print</span>(<span class="string">&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span>)</span><br><span class="line">èæ³</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">print</span>(<span class="string">b&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span>)</span><br><span class="line"><span class="string">b&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">print</span>(<span class="string">b&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span>.decode(<span class="string">&#x27;utf8&#x27;</span>))</span><br><span class="line">膜法</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">print</span>(<span class="string">&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span>)</span><br><span class="line">èæ³</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">print</span>(<span class="string">b&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span>.decode(<span class="string">&#x27;utf8&#x27;</span>))</span><br><span class="line">膜法</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="string">&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span>.encode(<span class="string">&#x27;latin-1&#x27;</span>)</span><br><span class="line"><span class="string">b&#x27;\xe8\x86\x9c\xe6\xb3\x95&#x27;</span></span><br></pre></td></tr></table></figure>

<h3 id="文件读写"><a href="#文件读写" class="headerlink" title="文件读写"></a>文件读写</h3><p>open函数，与unicode的问题是相关联的</p>
<ul>
<li>python2的文件读写都是操作bytes的</li>
<li>python3的文件读写，默认是<code>t</code>(文本)模式的，操作的是unicode，并以utf8作为默认编码</li>
</ul>
<p>python2</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>aa = <span class="built_in">open</span>(<span class="string">&#x27;/mnt/d/11.txt&#x27;</span>, <span class="string">&#x27;r&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>aa.readline()</span><br><span class="line"><span class="string">&#x27; \xe9\xa9\xb1\xe5\x8a\xa8\xe5\x99\xa8 F \xe4\xb8\xad\xe7\x9a\x84\xe5\x8d\xb7\xe6\x98\xaf 20090108_171210\r\n&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>bb = <span class="built_in">open</span>(<span class="string">&#x27;/mnt/d/11.txt&#x27;</span>, <span class="string">&#x27;rb&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>bb.readline()</span><br><span class="line"><span class="string">&#x27; \xe9\xa9\xb1\xe5\x8a\xa8\xe5\x99\xa8 F \xe4\xb8\xad\xe7\x9a\x84\xe5\x8d\xb7\xe6\x98\xaf 20090108_171210\r\n&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cc =  <span class="built_in">open</span>(<span class="string">&#x27;/mnt/d/11_gbk.txt&#x27;</span>, <span class="string">&#x27;rb&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cc.readline()</span><br><span class="line"><span class="string">&#x27; \xc7\xfd\xb6\xaf\xc6\xf7 F \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 20090108_171210&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>dd =  <span class="built_in">open</span>(<span class="string">&#x27;/mnt/d/11_gbk.txt&#x27;</span>, <span class="string">&#x27;r&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>dd.readline()</span><br><span class="line"><span class="string">&#x27; \xc7\xfd\xb6\xaf\xc6\xf7 F \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 20090108_171210&#x27;</span></span><br></pre></td></tr></table></figure>

<p>python3</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span> aa = <span class="built_in">open</span>(<span class="string">&#x27;/mnt/d/11.txt&#x27;</span>, <span class="string">&#x27;r&#x27;</span>)  <span class="comment"># 11.txt is saved as utf8</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>aa.readline()</span><br><span class="line"><span class="string">&#x27; 驱动器 F 中的卷是 20090108_171210\n&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>bb = <span class="built_in">open</span>(<span class="string">&#x27;/mnt/d/11.txt&#x27;</span>, <span class="string">&#x27;rb&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>bb.readline()</span><br><span class="line"><span class="string">b&#x27; \xe9\xa9\xb1\xe5\x8a\xa8\xe5\x99\xa8 F \xe4\xb8\xad\xe7\x9a\x84\xe5\x8d\xb7\xe6\x98\xaf 20090108_171210\r\n&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cc =  <span class="built_in">open</span>(<span class="string">&#x27;/mnt/d/11_gbk.txt&#x27;</span>, <span class="string">&#x27;rb&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cc.readline()</span><br><span class="line"><span class="string">b&#x27; \xc7\xfd\xb6\xaf\xc6\xf7 F \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 20090108_171210&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>dd = <span class="built_in">open</span>(<span class="string">&#x27;/mnt/d/11_gbk.txt&#x27;</span>, <span class="string">&#x27;r&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>dd.readline()</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;&lt;ipython-input-10-ba8f5810b020&gt;&quot;</span>, line <span class="number">1</span>, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    dd.readline()</span><br><span class="line">  File <span class="string">&quot;/home/jayruan/.pyenv/versions/3.6.1/lib/python3.6/codecs.py&quot;</span>, line <span class="number">321</span>, <span class="keyword">in</span> decode</span><br><span class="line">    (result, consumed) = <span class="variable language_">self</span>._buffer_decode(data, <span class="variable language_">self</span>.errors, final)</span><br><span class="line">UnicodeDecodeError: <span class="string">&#x27;utf-8&#x27;</span> codec can<span class="string">&#x27;t decode byte 0xc7 in position 1: invalid continuation byte</span></span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>django 登录后的跳转到之前的页面</title>
    <url>/2015/11/25/django%20%E7%99%BB%E5%BD%95%E5%90%8E%E7%9A%84%E8%B7%B3%E8%BD%AC%E9%97%AE%E9%A2%98/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>在开发django网站时发现，用户登录后不能跳转到之前的页面，google了很多答案，讲得也不清楚。</p>
<p>其实就是渲染登陆表单时，将原链接带到action参数里，view函数接收到参数后进行重定向。</p>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><h3 id="登录链接"><a href="#登录链接" class="headerlink" title="登录链接"></a>登录链接</h3><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/account/login/?next=&#123;&#123;request.path&#125;&#125;&quot;</span>&gt;</span>登录<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h3 id="view"><a href="#view" class="headerlink" title="view"></a>view</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">login</span>(<span class="params">request</span>):</span><br><span class="line">    next_url = request.REQUEST.get(<span class="string">&#x27;next&#x27;</span>, <span class="string">&#x27;/&#x27;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> request.method == <span class="string">&#x27;GET&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> render_to_response(<span class="string">&#x27;account/login.html&#x27;</span>, &#123;<span class="string">&#x27;next_url&#x27;</span>: next_url&#125;, context_instance=RequestContext(request))</span><br><span class="line"></span><br><span class="line">    django_login(request, user)</span><br><span class="line">    <span class="keyword">return</span> redirect(next_url)</span><br></pre></td></tr></table></figure>

<h3 id="登录表单"><a href="#登录表单" class="headerlink" title="登录表单"></a>登录表单</h3><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">action</span>=<span class="string">&quot;/account/login/?next=&#123;&#123;next_url&#125;&#125;&quot;</span> <span class="attr">method</span>=<span class="string">&quot;post&quot;</span> &gt;</span></span><br><span class="line">&#123;% csrf_token %&#125;</span><br><span class="line">    用户名<span class="tag">&lt;<span class="name">input</span> <span class="attr">id</span>=<span class="string">&quot;username&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span>  <span class="attr">required</span>/&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">br</span>&gt;</span></span><br><span class="line">    密码<span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;password&quot;</span> <span class="attr">name</span>=<span class="string">&quot;password&quot;</span> <span class="attr">required</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">value</span>=<span class="string">&quot;登  录&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br></pre></td></tr></table></figure>





]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Django</tag>
      </tags>
  </entry>
  <entry>
    <title>django-admin过滤foreignkey值</title>
    <url>/2015/11/25/django-admin%E8%BF%87%E6%BB%A4foreignkey%E5%80%BC/</url>
    <content><![CDATA[<h2 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h2><p>在用django的admin进行管理的时候，对于指定的用户角色，不希望他看到特定状态的foreignkey，可以采用以下方案。</p>
<p>当然，也可通过自定义form解决该问题。</p>
<h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FactoryOrderItemInline</span>(admin.TabularInline):</span><br><span class="line">    model = FactoryOrderItem</span><br><span class="line">    fields = (<span class="string">&#x27;order_item&#x27;</span>, <span class="string">&#x27;product_sn&#x27;</span>, <span class="string">&#x27;style&#x27;</span>, <span class="string">&#x27;size&#x27;</span>, <span class="string">&#x27;factory_sn&#x27;</span>, <span class="string">&#x27;price&#x27;</span>, <span class="string">&#x27;quantity&#x27;</span>, <span class="string">&#x27;amount&#x27;</span>)</span><br><span class="line">    extra = <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">formfield_for_foreignkey</span>(<span class="params">self, db_field, request, **kwargs</span>):</span><br><span class="line">        <span class="keyword">if</span> db_field.name == <span class="string">&#x27;order_item&#x27;</span>:</span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                parent_obj_id = request.resolver_match.args[<span class="number">0</span>]</span><br><span class="line">            <span class="keyword">except</span> IndexError: <span class="comment">#仅在新建状态过滤</span></span><br><span class="line">                kwargs[<span class="string">&#x27;queryset&#x27;</span>] = OrderItem.objects.<span class="built_in">filter</span>(order__status=<span class="string">&#x27;2&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>(FactoryOrderItemInline, <span class="variable language_">self</span>).formfield_for_foreignkey(db_field, request, **kwargs)</span><br></pre></td></tr></table></figure>]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Django</tag>
      </tags>
  </entry>
  <entry>
    <title>Docker mtu 引发的加班血案</title>
    <url>/2021/09/21/docker-mtu-issues/</url>
    <content><![CDATA[<p>最近在搞 torch 的工程化，基于 brpc 和 libtorch，将两者编译在一起的过程也是坑深，容下次再表。</p>
<p>为了简化部署，brpc 服务在 Docker 容器中运行。本地测试时功能一切正常，上到预发布环境时请求全部超时。</p>
<p>由于业务代码，brpc，docker环境，机房都是新的，在排查问题的过程中简直一头雾水。（当然根本原因还是水平不足）</p>
<span id="more"></span>
<h2 id="使尽浑身解数定位"><a href="#使尽浑身解数定位" class="headerlink" title="使尽浑身解数定位"></a>使尽浑身解数定位</h2><p>发现请求超时后，开始用CURL测试接口，用真实数据验证发现请求都耗时1s，这和用c++的预期完全不符。</p>
<h3 id="我代码不应该有bug"><a href="#我代码不应该有bug" class="headerlink" title="我代码不应该有bug!!"></a>我代码不应该有bug!!</h3><p>首先是怀疑业务代码有问题，逐行统计业务代码耗时，发现业务代码仅耗时10+ms。</p>
<h3 id="curl-不应该-Expect"><a href="#curl-不应该-Expect" class="headerlink" title="curl 不应该 Expect"></a>curl 不应该 Expect</h3><p>用空数据访问接口，发现耗时也只有20+ms，这时开始怀疑brpc是不是编译得有问题，或者说和libtorch编译到一起不兼容。<br>这时我请教了一位同事，他对brpc比较熟悉，然后他说是curl实现的问题，和brpc没关系, 参考<a href="https://github.com/apache/incubator-brpc/issues/1075"> brpc issue</a></p>
<figure class="highlight kotlin"><table><tr><td class="code"><pre><span class="line">curl传输的时候，会设置 Expect: <span class="number">100</span>-<span class="keyword">continue</span>, 这个协议brpc本身没有实现, 所以curl会等待一个超时。</span><br><span class="line">加上 -H <span class="string">&#x27;Expect:&#x27;</span> 可以解决这个问题</span><br></pre></td></tr></table></figure>

<p>所以这个1s超时是个烟雾弹，线上client是Python，不会有这个问题。</p>
<h3 id="难道是-lvs"><a href="#难道是-lvs" class="headerlink" title="难道是 lvs ?"></a>难道是 lvs ?</h3><p>接着又是一通疯狂测试，各种角度体位测试。发现本机测试是ok的，透过lvs请求（跨机房）就会卡住直到超时，而且小body请求一切正常，大body请求卡住。</p>
<p>这时又开始怀疑brpc编译的不对，导致这个超时（brpc编译过程比较曲折，导致我不太有信心）。</p>
<p>于是我在这个服务器Docker中运行了另一个brpc服务，发现是一样的问题。</p>
<p>为了确认是否是brpc的问题，又写了个Python 的 echo server 进行测试，发现在docker中是一样的问题，但是不在docker中运行有一切正常。</p>
<p>这是时就可以确定，与代码无关，是docker或者lvs的问题，一度陷入僵局。</p>
<h3 id="docker-才是罪魁祸首"><a href="#docker-才是罪魁祸首" class="headerlink" title="docker 才是罪魁祸首"></a>docker 才是罪魁祸首</h3><p>完全没思路，于是找来了运维的同学，这时运维提了一下，这个机房的mtu会小一点，于是一切串起来了。</p>
<p>马上测试，发现机房的mtu只有1450，而docker0网桥的默认mtu是1500，这就很能解释小body没问题，大body卡死。</p>
<figure class="highlight tap"><table><tr><td class="code"><pre><span class="line">~<span class="comment"># netstat -i</span></span><br><span class="line">Kernel Interface table</span><br><span class="line">Iface      MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg</span><br><span class="line">docker0  <span class="number"> 1500 </span>      <span class="number"> 0 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>0            <span class="number"> 0 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>BMU</span><br><span class="line">eth0     <span class="number"> 1450 </span><span class="number"> 1023246 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>0       <span class="number"> 110268 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>BMRU</span><br><span class="line">lo      <span class="number"> 65536 </span>   <span class="number"> 1967 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>0         <span class="number"> 1967 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>LRU</span><br><span class="line">wlan0    <span class="number"> 1500 </span>      <span class="number"> 0 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>0            <span class="number"> 0 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>    <span class="number"> 0 </span>BMU</span><br></pre></td></tr></table></figure>

<p>修改docker0的mtu，重启docker.service，一切问题都解决了。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt; <span class="string">EOF &gt; /etc/docker/daemon.json</span></span><br><span class="line"><span class="string">&#123;</span></span><br><span class="line"><span class="string">  &quot;mtu&quot;: 1450</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure>

<h2 id="事后诸葛"><a href="#事后诸葛" class="headerlink" title="事后诸葛"></a>事后诸葛</h2><h3 id="找到问题等于解决了一半"><a href="#找到问题等于解决了一半" class="headerlink" title="找到问题等于解决了一半"></a>找到问题等于解决了一半</h3><p>这句话说得并不太懂，应该是“找到问题等于解决了90%”。<br>在互联网时代，找到了具体问题，在一通Google，基本等于解决了问题。你始终要确信，这个问题不应该只有你遇到。</p>
<p>在这个例子上，curl的误导大概花了一半的时间去定位。所以，定位问题首先得明确问题，如医生看病一样，确认问题发生的现象（卡住），位置(docker容器中)、程度（永久）、触发原因（大请求body）。</p>
<h3 id="要善于运用他人的经验和知识"><a href="#要善于运用他人的经验和知识" class="headerlink" title="要善于运用他人的经验和知识"></a>要善于运用他人的经验和知识</h3><p>找专业人士寻求帮助是非常高效的，会大大缩短定位问题的时间，因为他们会运用经验和知识快速排除错误选项。</p>
<h3 id="知识可能会误导你"><a href="#知识可能会误导你" class="headerlink" title="知识可能会误导你"></a>知识可能会误导你</h3><p>你所拥有的知识，并不是究竟的知识，也就是它所能应用的范围并不适应当前的场景，还有可能误导你。<br>你所缺失的知识，让你看不清前方正确的道路。</p>
<p>按我的知识，docker0是网桥，等价于交换机，除了性能问题，不应该导致丢包，就一直没往这个方向考虑（当然这块的知识也不扎实）。<br>MTU也不应该导致丢包，交换机应该会进行IP分片。<br>但忽略了一个点，虚拟的网桥并不是硬件网桥，可能并没有实现IP分片的逻辑（仅丢弃），又或者没有实现 PMTU（Path MTU Discovery）。<br>上面这点存疑，但更直接的原因是服务的IP包的 <code>Don&#39;t fragment</code> flag 为1，也就是禁止分片（为什么设置还不清楚）。<br><img data-src="https://image.ponder.work/mweb/2022-07-11-16575342644060.jpg"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://github.com/apache/incubator-brpc/issues/1075">https://github.com/apache/incubator-brpc/issues/1075</a></li>
<li><a href="https://mp.weixin.qq.com/s/-Q1AkxUr9xzGKwUMV-FQhQ">云网络丢包故障定位全景指南</a></li>
<li><a href="https://mlohr.com/docker-mtu/">https://mlohr.com/docker-mtu/</a></li>
<li><a href="https://github.com/docker/for-win/issues/1144">https://github.com/docker/for-win/issues/1144</a></li>
<li><a href="https://www.cnblogs.com/sammyliu/p/5079898.html">https://www.cnblogs.com/sammyliu/p/5079898.html</a></li>
<li><a href="https://github.com/moby/moby/issues/12565">https://github.com/moby/moby/issues/12565</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Docker</tag>
      </tags>
  </entry>
  <entry>
    <title>Python2 中 enum 枚举库诡异行为探究</title>
    <url>/2019/11/21/enum-lib-problem-in-python2/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>最近接手了一个82年的<del>拉菲</del>Python项目，简直酸爽。<br>Python版本用的是2.7.5，里面用的第三方库各种老旧，潜藏了不少深坑。这次我们就先讲enum库的坑，其他有空再聊。</p>
<p>enum是一个枚举类型的第三方库，在Python3.4以后就作为官方库存在了，<a href="https://www.liaoxuefeng.com/wiki/1016959663602400/1017595944503424">使用参考</a>。<br>所以如果用新版本的Python就不会存在这个问题了，版本老旧害死人啊。</p>
<span id="more"></span>

<h2 id="症状"><a href="#症状" class="headerlink" title="症状"></a>症状</h2><p>刚接手这个项目，在搭建环境的时候，装完依赖发现enum库不能按照预期工作（没有完善的requirements.txt是个坑）</p>
<p><strong>正式环境</strong></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">import</span> sys</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>sys.version</span><br><span class="line"><span class="string">&#x27;2.7.5 (default, Nov  2 2019, 14:10:22) \n[GCC 4.4.7 20120313 (Red Hat 4.4.7-23)]&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> enum <span class="keyword">import</span> Enum</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">Weekday</span>(<span class="title class_ inherited__">Enum</span>):</span><br><span class="line"><span class="meta">... </span>    Sun = <span class="number">0</span> <span class="comment"># Sun的value被设定为0</span></span><br><span class="line"><span class="meta">... </span>    Mon = <span class="number">1</span></span><br><span class="line"><span class="meta">... </span>    Tue = <span class="number">2</span></span><br><span class="line"><span class="meta">... </span>    Wed = <span class="number">3</span></span><br><span class="line"><span class="meta">... </span>    Thu = <span class="number">4</span></span><br><span class="line"><span class="meta">... </span>    Fri = <span class="number">5</span></span><br><span class="line"><span class="meta">... </span>    Sat = <span class="number">6</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(Weekday.Sun)</span><br><span class="line">&lt;<span class="built_in">type</span> <span class="string">&#x27;int&#x27;</span>&gt;</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Weekday.Sun</span><br><span class="line"><span class="number">0</span></span><br></pre></td></tr></table></figure>

<p><strong>开发环境</strong></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>sys.version</span><br><span class="line"><span class="string">&#x27;2.7.16 (default, Oct 31 2019, 06:57:54) \n[GCC 4.4.7 20120313 (Red Hat 4.4.7-23)]&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> enum <span class="keyword">import</span> Enum</span><br><span class="line"><span class="meta">... </span><span class="keyword">class</span> <span class="title class_">Weekday</span>(<span class="title class_ inherited__">Enum</span>):</span><br><span class="line"><span class="meta">... </span>    Sun = <span class="number">0</span> <span class="comment"># Sun的value被设定为0</span></span><br><span class="line"><span class="meta">... </span>    Mon = <span class="number">1</span></span><br><span class="line"><span class="meta">... </span>    Tue = <span class="number">2</span></span><br><span class="line"><span class="meta">... </span>    Wed = <span class="number">3</span></span><br><span class="line"><span class="meta">... </span>    Thu = <span class="number">4</span></span><br><span class="line"><span class="meta">... </span>    Fri = <span class="number">5</span></span><br><span class="line"><span class="meta">... </span>    Sat = <span class="number">6</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">type</span>(Weekday.Sun)</span><br><span class="line">&lt;enum <span class="string">&#x27;Weekday&#x27;</span>&gt;</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Weekday.Sun.value</span><br><span class="line"><span class="number">0</span></span><br></pre></td></tr></table></figure>

<p>很明显可以看到，<code>Weekday.Sun</code>的行为在两个环境不一致（其实第一个库用法不对）。</p>
<ul>
<li>正式环境：<code>Weekday.Sun</code>的类型是<code>int</code></li>
<li>我的开发环境: <code>Weekday.Sun</code>的类型是<code>&lt;enum &#39;Weekday&#39;&gt;</code></li>
</ul>
<h2 id="归因"><a href="#归因" class="headerlink" title="归因"></a>归因</h2><p>一开始我以为的enum库的bug，在不同版本的Python中行为不一致。</p>
<p>打开库源码路径一看，还真是大吃一惊。</p>
<p>原来两个环境的enum库压根就不一样，实现方式也不太一样，所以行为不一样。</p>
<h3 id="正式环境的库"><a href="#正式环境的库" class="headerlink" title="正式环境的库"></a>正式环境的库</h3><p>安装的是：enum&#x3D;&#x3D;0.4.7 <a href="https://pypi.org/project/enum/">pypi</a></p>
<p>目录结构:</p>
<figure class="highlight smali"><table><tr><td class="code"><pre><span class="line">enum.py</span><br><span class="line">enum-0.4.7-py2.7.egg-info</span><br><span class="line">├── PKG-INFO</span><br><span class="line">├── SOURCES.txt</span><br><span class="line">├── dependency_links.txt</span><br><span class="line">├── installed-files.txt</span><br><span class="line">├──<span class="built_in"> not-zip-safe</span></span><br><span class="line"><span class="built_in"></span>├── requires.txt</span><br><span class="line">└── top_level.txt</span><br></pre></td></tr></table></figure>

<p>只包含了enum.py这个文件，而且里面的代码实现很糟糕，和Python官方库的行为不一致。<br>也没找到源码管理仓库，总体来说质量糟糕。</p>
<h3 id="开发环境的库"><a href="#开发环境的库" class="headerlink" title="开发环境的库"></a>开发环境的库</h3><p>安装的是：enum34&#x3D;&#x3D;1.1.6 <a href="https://pypi.org/project/enum34/">pypi</a></p>
<p>目录结构:</p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">enum</span><br><span class="line">├── LICENSE</span><br><span class="line">├── README</span><br><span class="line">├── __init__<span class="selector-class">.py</span></span><br><span class="line">└── __init__<span class="selector-class">.pyc</span></span><br><span class="line">enum34-<span class="number">1.1</span>.<span class="number">6</span><span class="selector-class">.dist-info</span></span><br><span class="line">├── DESCRIPTION<span class="selector-class">.rst</span></span><br><span class="line">├── INSTALLER</span><br><span class="line">├── METADATA</span><br><span class="line">├── RECORD</span><br><span class="line">├── WHEEL</span><br><span class="line">├── metadata<span class="selector-class">.json</span></span><br><span class="line">└── top_level.txt</span><br></pre></td></tr></table></figure>

<p>和上面的库不同的是，这个库是个Python包，通过元类的方法实现枚举类，行为和Python3的官方库一致。<br>代码质量较高，并且有Python2和Python3的兼容代码。</p>
<h3 id="然而"><a href="#然而" class="headerlink" title="然而"></a>然而</h3><p>这个老项目用的是第一个库，牵涉的东西比较多，暂时不能改动，只能将错就错。</p>
<h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><h3 id="pip和pypi的问题"><a href="#pip和pypi的问题" class="headerlink" title="pip和pypi的问题"></a>pip和pypi的问题</h3><ol>
<li>库名称和Python包、模块名称可能是不一致的。</li>
<li>pip安装的时候，没有对同名包、模块做冲突检测</li>
</ol>
<p>这个两个问题都与Python Zen的“显式胜于隐含”的理念相冲突，这个是很不应该的。</p>
<h3 id="同名模块和包优先级"><a href="#同名模块和包优先级" class="headerlink" title="同名模块和包优先级"></a>同名模块和包优先级</h3><p>在同一目录存在同名包和模块的情况下，疑似Python会优先加载包，没有查到确切的文档。</p>
<p>具体的加载行为，后面看一下源码分析一下。</p>
<h3 id="技术人员知识储备"><a href="#技术人员知识储备" class="headerlink" title="技术人员知识储备"></a>技术人员知识储备</h3><p>一开始调侃说，82年的Python项目。其实这个项目是在2017年左右启动的，但是里面使用的大部分是过时的技术。</p>
<p>为何呢？无非是舒适区，或者说紧跟时代是一件挺难的事情。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Enum</tag>
      </tags>
  </entry>
  <entry>
    <title>esim 使用相关</title>
    <url>/2026/03/04/esim-usage/</url>
    <content><![CDATA[<p>国行手机的 esim 功能有较多限制，使用不太方便。<br>但我们可以使用 esim 适配器，将 esim 转为实体 sim 卡。</p>
<span id="more"></span>
<h2 id="esim-适配器"><a href="#esim-适配器" class="headerlink" title="esim 适配器"></a>esim 适配器</h2><p>esim 适配器就是个 sim 卡，需要占用一个卡槽，可以通过软件将 esim 卡号写入到适配器。<br>可以写入多个 esim 号码，并且支持切换，但同时只能有一个在线。<br>适配器有不少品牌，如 estk、5ber、9esim，价格也各不相同，但是我都没怎么试过。<br>我使用的的适配器是 pdd 购买，20-30元，容量468KB。</p>
<h2 id="esim-套餐"><a href="#esim-套餐" class="headerlink" title="esim 套餐"></a>esim 套餐</h2><p>一般海外套餐都可以使用，我这里只用于接收短信，故使用 clubsim，成本大概50元。</p>
<h2 id="号码写入"><a href="#号码写入" class="headerlink" title="号码写入"></a>号码写入</h2><p>购买套餐后，运营商会提供二维码，安卓手机通过 <a href="https://gitea.angry.im/PeterCxy/OpenEUICC">OpenEUICC</a> 扫描就可以写入和管理 esim。</p>
<p>如果是 iphone，需要使用 esim 读卡器来管理 esim 号码。</p>
<h2 id="使用感受"><a href="#使用感受" class="headerlink" title="使用感受"></a>使用感受</h2><p>esim 还是挺方便的，用于接收验证码非常够用，还有些免费流量的卡。<br>但是切换 esim 号码时，可能需要切换飞行模式或者重启手机才能生效，不知是通病还是兼容性问题。</p>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>由于安卓操作系统（11+）的 bug，在“移动网络”中禁用 esim 适配器会无法打开。<br>需要通过 adb 清除移动网络的数据才能恢复</p>
<figure class="highlight gauss"><table><tr><td class="code"><pre><span class="line">adb <span class="built_in">shell</span> pm <span class="keyword">clear</span> com.android.providers.telephony</span><br></pre></td></tr></table></figure>

<p>如果是小米等手机，清除过程可能会遇到adb权限相关问题<br>需要额外开启开发者选项中的“OEM解锁”和“USB调试（安全设置）”</p>
<figure class="highlight vim"><table><tr><td class="code"><pre><span class="line">adb <span class="keyword">shell</span> pm clear <span class="keyword">com</span>.android.providers.telephony</span><br><span class="line"></span><br><span class="line">Exception occurred <span class="keyword">while</span> executing <span class="string">&#x27;clear&#x27;</span>:</span><br><span class="line">java.lang.SecurityException: PID <span class="number">29160</span> does not have permission android.permission.CLEAR_APP_USER_DATA <span class="keyword">to</span> clear data of package <span class="keyword">com</span>.android.providers.telephony</span><br><span class="line">	at <span class="keyword">com</span>.android.server.<span class="keyword">am</span>.ActivityManagerService.clearApplicationUserData(ActivityManagerService.jav<span class="variable">a:4101</span>)</span><br><span class="line">	at <span class="keyword">com</span>.android.server.<span class="keyword">am</span>.ActivityManagerService.clearApplicationUserData(ActivityManagerService.jav<span class="variable">a:4054</span>)</span><br><span class="line">	at <span class="keyword">com</span>.android.server.pm.PackageManagerShellCommand.runClear(PackageManagerShellCommand.jav<span class="variable">a:2385</span>)</span><br><span class="line">	at <span class="keyword">com</span>.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.jav<span class="variable">a:289</span>)</span><br><span class="line">	at <span class="keyword">com</span>.android.modules.utils.BasicShellCommandHandler.exec(BasicShellCommandHandler.jav<span class="variable">a:97</span>)</span><br><span class="line">	at android.os.ShellCommand.exec(ShellCommand.jav<span class="variable">a:38</span>)</span><br><span class="line">	at <span class="keyword">com</span>.android.server.pm.PackageManagerService$IPackageManagerImpl.onShellCommand(PackageManagerService.jav<span class="variable">a:7022</span>)</span><br><span class="line">	at android.os.Binder.shellCommand(Binder.jav<span class="variable">a:1158</span>)</span><br><span class="line">	at android.os.Binder.onTransact(Binder.jav<span class="variable">a:960</span>)</span><br><span class="line">	at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.jav<span class="variable">a:4729</span>)</span><br><span class="line">	at <span class="keyword">com</span>.android.server.pm.PackageManagerService$IPackageManagerImpl.onTransact(PackageManagerService.jav<span class="variable">a:7006</span>)</span><br><span class="line">	at android.os.Binder.execTransactInternal(Binder.jav<span class="variable">a:1433</span>)</span><br><span class="line">	at android.os.Binder.execTransact(Binder.jav<span class="variable">a:1372</span>)</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://linux.do/t/topic/1276447/5">https://linux.do/t/topic/1276447/5</a></li>
<li><a href="https://v2.b2og.com/archives/15">https://v2.b2og.com/archives/15</a></li>
</ul>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>Android</tag>
        <tag>esim</tag>
      </tags>
  </entry>
  <entry>
    <title>git-svn：通过git来管理svn代码</title>
    <url>/2016/09/10/git-svn%E2%80%94%E2%80%94%E7%94%A8git%E6%9D%A5%E6%93%8D%E4%BD%9Csvn%E9%A1%B9%E7%9B%AE/</url>
    <content><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>svn和git都是常用的版本管理软件，但是git无论在理念或是功能上都比svn更为先进。但是有的公司是以svn作为中央仓库，这时git与svn代码的同步就可以通过 git-svn这个软件进行，从而用git管理svn代码。最后的效果相当于把svn仓库当作git的一个remote（远程仓库），而你本地的代码都是通过git来管理，只有push到svn时才会把你本地的commit同步到svn。</p>
<span id="more"></span>
<h2 id="从svn克隆"><a href="#从svn克隆" class="headerlink" title="从svn克隆"></a>从svn克隆</h2><p>首先看一看用于测试的svn项目结构，svn的仓库路径是<code>file:///d/Projects/svn_repo</code>，可以用<code>svnadmin create svn_repo</code>命令新建。该仓库有2个分支，1个tag，属于svn标准布局。</p>
<p>SVN项目结构：</p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">/d/proj1</span><br><span class="line">├── branches</span><br><span class="line">│   ├── <span class="selector-tag">a</span></span><br><span class="line">│   │   └── readme<span class="selector-class">.txt</span></span><br><span class="line">│   └── <span class="selector-tag">b</span></span><br><span class="line">│       ├── <span class="number">11</span><span class="selector-class">.txt</span></span><br><span class="line">│       └── readme<span class="selector-class">.txt</span></span><br><span class="line">├── tags</span><br><span class="line">│   └── v1.<span class="number">0</span></span><br><span class="line">│       ├── <span class="number">11</span><span class="selector-class">.txt</span></span><br><span class="line">│       └── readme<span class="selector-class">.txt</span></span><br><span class="line">└── trunk</span><br><span class="line">    └── readme.txt</span><br></pre></td></tr></table></figure>

<p>命令格式：<code>git svn clone &lt;svn仓库路径&gt; [本地文件夹名] [其他参数]</code> 相当于<code>git clone</code><br>示例： <code>git svn clone file:///d/Projects/svn_repo proj1_git -s --prefix=svn/</code><br>参数说明：</p>
<ul>
<li><code>-s</code> 告诉 Git 该 Subversion 仓库遵循了基本的分支和标签命名法则，也就是标准布局。<br>如果你的主干(trunk，相当于非分布式版本控制里的master分支，代表开发的主线），分支(branches)或者标签(tags)以不同的方式命名，则应做出相应改变。<br><code>-s</code>参数其实是<code>-T trunk -b branches -t tags</code>的缩写，这些参数告诉git这些文件夹与git分支、tag、master的对应关系。</li>
<li><code>--prefix=svn/</code> 给svn的所有remote名称增加了一个前缀svn，这样比较统一，而且可以防止<code>warning: refname &#39;xxx&#39; is ambiguous.</code></li>
</ul>
<p>现在，看下用git-svn克隆的项目情况（运行git branch -a），此处git的分支情况是与svn文件夹对应的。</p>
<figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">* master</span><br><span class="line">  remotes<span class="regexp">/svn/</span>a</span><br><span class="line">  remotes<span class="regexp">/svn/</span>b</span><br><span class="line">  remotes<span class="regexp">/svn/</span>tags/v1.<span class="number">0</span></span><br><span class="line">  remotes<span class="regexp">/svn/</span>trunk</span><br></pre></td></tr></table></figure>

<h3 id="只下载指定版本之后的历史"><a href="#只下载指定版本之后的历史" class="headerlink" title="只下载指定版本之后的历史"></a>只下载指定版本之后的历史</h3><p>如果svn上的commit次数非常多, git svn clone 就会非常慢，一般超过几百个版本就要大概十分钟。此时可以在clone的时候只下载部分版本，<br>命令：<code>git svn clone -r&lt;开始版本号&gt;:&lt;结束版本号&gt; &lt;svn项目地址&gt; [其他参数]</code><br>示例：<code>git svn clone -r2:HEAD file:///d/Projects/svn_repo proj1_git -s</code><br>说明：其中2为svn版本号，HEAD代表最新版本号，就是只下载svn服务器上版本2到最新的版本的代码.</p>
<h2 id="工作流程"><a href="#工作流程" class="headerlink" title="工作流程"></a>工作流程</h2><p>简单来说就是，首次新建分支会记录和svn远程对应分支的追踪关系，之后你的所有commit都是在本地的；并且和纯git管理的项目没有区别，只是在<code>git svn rebase</code>和<code>git svn dcommit</code>的时候才会和svn仓库发生关系</p>
<h3 id="一般工作流程（推荐）"><a href="#一般工作流程（推荐）" class="headerlink" title="一般工作流程（推荐）"></a>一般工作流程（推荐）</h3><ol>
<li>新建分支<code>git checkout -b &lt;本地分支名称&gt; &lt;远程分支名称&gt;</code><br>示例：<code>git checkout -b a svn/a</code><br>说明：此处新建了一个本地分支a，与svn的a分支对应。</li>
<li>在本地工作，commit到对应分支上</li>
<li><code>git svn rebase</code> 从svn上更新代码, 相当于svn的update。</li>
<li><code>git svn dcommit</code> 提交你的commit到svn远程仓库，建议提交前都先运行下git svn rebase。</li>
</ol>
<h3 id="在git本地其他分支工作的情况"><a href="#在git本地其他分支工作的情况" class="headerlink" title="在git本地其他分支工作的情况"></a>在git本地其他分支工作的情况</h3><ol>
<li><code>git chechout -b a svn/a</code> 此处新建了一个本地分支a，与svn的a分支对应。</li>
<li><code>git checkout -b feature1</code> 在a分支的基础上，开一个本地feture1分支</li>
<li>在feture1分支进行开发，有了多次commit</li>
<li>在feture1分支上进行<code>git svn rebase</code> 和 <code>git svn dcommit</code>，这样feature1的commit也会提交到svn的a分支上。<br>需要注意的是要记住feture1是从哪个分支checkout的，它的svn远程分支就与哪个相同。比如此处是a分支，那么svn分支就是svn&#x2F;a，commit就会提交到svn的a分支。</li>
</ol>
<h2 id="SVN分支管理"><a href="#SVN分支管理" class="headerlink" title="SVN分支管理"></a>SVN分支管理</h2><h3 id="新建分支到svn"><a href="#新建分支到svn" class="headerlink" title="新建分支到svn"></a>新建分支到svn</h3><p>命令：<code>git svn branch &lt;分支名称&gt;</code><br>示例：<code>git svn branch c_by_git</code><br>说明：在svn仓库上建了了一个c_by_git分支<br>分支情况</p>
<figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">  a</span><br><span class="line">* master</span><br><span class="line">  remotes<span class="regexp">/svn/</span>a</span><br><span class="line">  remotes<span class="regexp">/svn/</span>b</span><br><span class="line">  remotes<span class="regexp">/svn/</span>c_by_git</span><br><span class="line">  remotes<span class="regexp">/svn/</span>tags/v1.<span class="number">0</span></span><br><span class="line">  remotes<span class="regexp">/svn/</span>trunk</span><br></pre></td></tr></table></figure>
<h3 id="删除svn分支"><a href="#删除svn分支" class="headerlink" title="删除svn分支"></a>删除svn分支</h3><ul>
<li>删除svn分支目录<code>svn rm &lt;svn分支路径&gt; -m &lt;commit信息&gt;</code><br>示例：<code>svn rm file:///d/Projects/svn_repo/branches/c_by_git -m &#39;rm branch&#39;</code></li>
<li>删除远程跟踪分支<code>git branch -D -r &lt;远程分支名称&gt;</code><br>示例：<code>git branch -D -r svn/c_by_git</code></li>
</ul>
<h2 id="SVN上tag管理"><a href="#SVN上tag管理" class="headerlink" title="SVN上tag管理"></a>SVN上tag管理</h2><h3 id="新建tag"><a href="#新建tag" class="headerlink" title="新建tag"></a>新建tag</h3><p>命令：<code>git svn tag &lt;tag名称&gt;</code><br>示例：<code>git svn tag v1.1</code><br>说明：在svn仓库上建了一个v1.1tag</p>
<h3 id="删除tag"><a href="#删除tag" class="headerlink" title="删除tag"></a>删除tag</h3><ol>
<li><p>删除svn目录<code>svn rm &lt;svntag路径&gt; -m &lt;commit信息&gt;</code><br>示例：<code>svn rm file:///d/Projects/svn_repo/tags/v1.1 -m &#39;rm tag&#39;</code></p>
</li>
<li><p>删除远程跟踪分支<code>git branch -D -r &lt;远程分支名称&gt;</code><br>示例：<code>git branch -D -r svn/tags/v1.1</code><br>说明：svn的tag和分支在git看来是一样的，所以此处还是用的git branch</p>
</li>
</ol>
<h2 id="冲突解决"><a href="#冲突解决" class="headerlink" title="冲突解决"></a>冲突解决</h2><p>如果本地和svn都进行了修改，则不能快速前进，git svn rebase 会出现错误。<br>这时应该按以下步骤操作：</p>
<ol>
<li><p>手动修改冲突文件，修改完成后<code>git add</code></p>
</li>
<li><p><code>git rebase --continue</code></p>
</li>
<li><p><code>git svn dcommit</code></p>
</li>
</ol>
<h2 id="svn不遵循规范的情况"><a href="#svn不遵循规范的情况" class="headerlink" title="svn不遵循规范的情况"></a>svn不遵循规范的情况</h2><p>**以上讲的都是svn仓库是标准的情况，如果不标准，则以下几个地方都会有所不同。**主要就是每个步骤基本都要添加svn的具体路径。<br>先看看，示例项目的结构，仓库路径是<code>file:///d/Projects/svn_repo2</code>。这个项目主分支是dev文件夹，branch1和tag1文件夹分别代表的是一个分支和tag。</p>
<p>svn项目结构：</p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">/d/proj2</span><br><span class="line">├── branch1</span><br><span class="line">│   └── file1<span class="selector-class">.txt</span></span><br><span class="line">├── dev</span><br><span class="line">│   └── file1<span class="selector-class">.txt</span></span><br><span class="line">└── tag1</span><br><span class="line">    └── file1.txt</span><br></pre></td></tr></table></figure>

<h3 id="从svn克隆-1"><a href="#从svn克隆-1" class="headerlink" title="从svn克隆"></a>从svn克隆</h3><p>命令：<code>git svn clone &lt;svn项目地址，要包含具体分支路径&gt; [本地文件夹名]</code><br>示例：<code>git svn clone file:///d/Projects/svn_repo2/dev proj2_svn</code></p>
<h3 id="添加远程分支信息"><a href="#添加远程分支信息" class="headerlink" title="添加远程分支信息"></a>添加远程分支信息</h3><p>命令：</p>
<ol>
<li><code>git config --add svn-remote.&lt;远程分支名称&gt;.url &lt;svn地址，要包含具体分支路径&gt;</code></li>
<li><code>git config --add svn-remote.&lt;远程分支名称&gt;.fetch :refs/remotes/&lt;远程分支名称&gt;</code></li>
</ol>
<p>示例：</p>
<ol>
<li><code>git config --add svn-remote.svn/branch1.url file:///d/Projects/svn_repo2/branch1</code></li>
<li><code>git config --add svn-remote.svn/branch1.fetch :refs/remotes/svn/branch1</code></li>
</ol>
<p>说明：此处的“远程分支名称”可以随意填写，只要这三个保持一致即可。建议都给他们增加<code>svn/</code>前缀，这样svn的所有分支显示起来会比较一致，与上面clone时的<code>--prefix=svn/</code>类似。</p>
<h3 id="新建本地分支，与svn对应"><a href="#新建本地分支，与svn对应" class="headerlink" title="新建本地分支，与svn对应"></a>新建本地分支，与svn对应</h3><p>命令：</p>
<ol>
<li><code>git svn fetch &lt;远程分支名称&gt;</code> 获取svn仓库该分支的代码</li>
<li><code>git checkout -b &lt;本地分支名&gt; &lt;远程分支名称&gt;</code></li>
</ol>
<p>示例：</p>
<ol>
<li><code>git svn fetch svn/branch1</code></li>
<li><code>git checkout -b branch1 svn/branch1</code></li>
</ol>
<p>分支情况：</p>
<figure class="highlight crmsh"><table><tr><td class="code"><pre><span class="line">* branch1</span><br><span class="line">  <span class="keyword">master</span></span><br><span class="line">  <span class="title">remotes</span>/git-svn</span><br><span class="line">  remotes/svn/branch1</span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Git</tag>
        <tag>SVN</tag>
      </tags>
  </entry>
  <entry>
    <title>gmtime 在多线程环境使用引发的 bug</title>
    <url>/2021/04/15/gmtime-multi-thread-problem/</url>
    <content><![CDATA[<p>话接<a href="/2021/03/25/xgboost-multi-thread-problem-debug-and-fix/">上文</a>，还是这个 C++ 模型服务，在并发请求的情况下，大概有0.01%的请求部分模型分数不对。定位这种问题，对一个Python程序员来说，真是苦手。还好，经过调整代码不断测试，最终完美解决了问题。</p>
<span id="more"></span>

<h2 id="线程安全"><a href="#线程安全" class="headerlink" title="线程安全"></a>线程安全</h2><p>对比数据，可以发现和请求中的某个时间字段有关。关键逻辑代码如下，主要是一个时间差的计算。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// string date_str = &quot;2019-01-01 00:00:00&quot;;</span></span><br><span class="line"><span class="comment">// string date_appl = &quot;2012-01-01 00:00:00&quot;;</span></span><br><span class="line"><span class="function"><span class="type">double</span> <span class="title">test_a</span><span class="params">(string&amp; date_str, string&amp; date_appl, tm&amp; tm_appl)</span> </span>&#123;</span><br><span class="line">    <span class="function">stringstream <span class="title">ss</span><span class="params">(date_str.substr(<span class="number">0</span>,<span class="number">10</span>))</span></span>;</span><br><span class="line">    <span class="type">int</span> date_sec;</span><br><span class="line">    ss &gt;&gt; date_sec;  <span class="comment">// stringstream to int </span></span><br><span class="line">    <span class="type">time_t</span> ts_date_1 = date_sec + <span class="number">8</span>*<span class="number">3600</span>;</span><br><span class="line">    tm* tm_date = <span class="built_in">gmtime</span>(&amp;ts_date_1);  <span class="comment">// timestemp to tm </span></span><br><span class="line">    tm_date-&gt;tm_hour = <span class="number">0</span>;</span><br><span class="line">    tm_date-&gt;tm_min = <span class="number">0</span>;</span><br><span class="line">    tm_date-&gt;tm_sec = <span class="number">0</span>;</span><br><span class="line">    <span class="type">double</span> seconds = <span class="built_in">difftime</span>(<span class="built_in">mktime</span>(&amp;tm_appl), <span class="built_in">mktime</span>(tm_date)); <span class="comment">// diff timestemp</span></span><br><span class="line">    <span class="keyword">return</span> seconds;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>根据之前的经验，肯定是该函数内部某些操作非线程安全的, 通过google搜索（关键词: gmtime thread safe）和询问朋友，得到以下信息。</p>
<ul>
<li>stringstream to int: 这里date_sec变量不存在竞争条件，所以安全</li>
<li>gmtime: 不安全</li>
<li>mktime: 时区不变的情况下安全</li>
<li>difftime: 安全</li>
</ul>
<p>bug应该是来自<code>gmtime</code>，该函数返回的是tm结构体指针，指向的是一个 static 结构体，所以不是线程安全，可以用<code>gmtime_r</code>函数替换。</p>
<p>修改之后</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">double</span> <span class="title">test_a</span><span class="params">(string&amp; date_str, string&amp; date_appl, tm&amp; tm_appl)</span> </span>&#123;</span><br><span class="line">    <span class="function">stringstream <span class="title">ss</span><span class="params">(date_str.substr(<span class="number">0</span>,<span class="number">10</span>))</span></span>;</span><br><span class="line">    <span class="type">int</span> date_sec;</span><br><span class="line">    ss &gt;&gt; date_sec;  <span class="comment">// stringstream to int </span></span><br><span class="line">    <span class="type">time_t</span> ts_date_1 = date_sec + <span class="number">8</span>*<span class="number">3600</span>;</span><br><span class="line">    tm tm_date;</span><br><span class="line">    <span class="built_in">gmtime_r</span>(&amp;ts_date_1, &amp;tm_date);</span><br><span class="line">    tm_date.tm_hour = <span class="number">0</span>;</span><br><span class="line">    tm_date.tm_min = <span class="number">0</span>;</span><br><span class="line">    tm_date.tm_sec = <span class="number">0</span>;</span><br><span class="line">    <span class="type">double</span> seconds = <span class="built_in">difftime</span>(<span class="built_in">mktime</span>(&amp;tm_appl), <span class="built_in">mktime</span>(&amp;tm_date));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>经过测试，bug得以解决。但是同时发现一个问题，程序的qps下降了1&#x2F;3。</p>
<h2 id="QPS下降"><a href="#QPS下降" class="headerlink" title="QPS下降"></a>QPS下降</h2><p>编程测试代码测试对比<code>gmtime_r</code>和<code>gmtime</code>耗时上并没有显著差别。而且如果不调用<code>gmtime_r</code>只声明<code>tm tm_date</code>，qps也是一样下降。</p>
<p>考虑到这个函数，在服务中调用次数比较多，而且自动变量的栈空间在函数调用时就会分配, tm结构体又比较大，应该对耗时有影响。</p>
<p>尝试修改<code>tm tm_date</code>为<code>static tm tm_date</code>静态分配内存, qps恢复正常了。<br>但是由于我们需要在多线程环境中使用，最终修改为<code>static thread_local tm tm_date</code>。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://man7.org/linux/man-pages/man3/gmtime.3p.html#DESCRIPTION">https://man7.org/linux/man-pages/man3/gmtime.3p.html#DESCRIPTION</a></li>
<li><a href="https://stackoverflow.com/questions/18355101/is-standard-c-mktime-thread-safe-on-linux#answer-18355323">https://stackoverflow.com/questions/18355101/is-standard-c-mktime-thread-safe-on-linux#answer-18355323</a></li>
<li><a href="https://man7.org/linux/man-pages/man3/difftime.3.html#ATTRIBUTES">https://man7.org/linux/man-pages/man3/difftime.3.html#ATTRIBUTES</a></li>
<li><a href="https://softwareengineering.stackexchange.com/questions/195385/understanding-stack-frame-of-function-call-in-c-c#answer-195406">https://softwareengineering.stackexchange.com/questions/195385/understanding-stack-frame-of-function-call-in-c-c#answer-195406</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>C++</tag>
      </tags>
  </entry>
  <entry>
    <title>容器内进程优雅退出</title>
    <url>/2022/07/10/gracefully-shutdown-container/</url>
    <content><![CDATA[<p>在使用 docker 时，常常会碰到进程退出时资源清理的问题，比如保证当前请求处理完成，再退出程序。</p>
<p>当执行 <code>docker stop xxx</code> 时，docker会向主进程（pid&#x3D;1）发送 <code>SIGTERM</code> 信号<br>如果在一定时间(默认为10s)内进程没有退出，会进一步发送 <code>SIGKILL</code> 直接杀死程序，该信号既不能被捕捉也不能被忽略。</p>
<p>一般的web框架或者rpc框架都集成了 <code>SIGTERM</code> 信号处理程序， 一般不用担心优雅退出的问题。<br>但是如果你的容器内有多个程序（称为胖容器，一般不推荐），那么就需要做一些操作保证所有程序优雅退出。</p>
<span id="more"></span>

<h2 id="signals"><a href="#signals" class="headerlink" title="signals"></a>signals</h2><p>信号是一种进程间通信机制，它给应用程序提供一种异步的软件中断，使应用程序有机会接受其他程序活终端发送的命令(即信号)。</p>
<p>应用程序收到信号后，有三种处理方式：忽略，默认，或捕捉。</p>
<p>常见信号：</p>
<table>
<thead>
<tr>
<th>信号名称</th>
<th>信号数</th>
<th>描述</th>
<th align="left">默认操作</th>
</tr>
</thead>
<tbody><tr>
<td>SIGHUP</td>
<td>1</td>
<td>当用户退出Linux登录时，前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。对于与终端脱离关系的守护进程，这个信号用于通知它重新读取配置文件。</td>
<td align="left">终止进程</td>
</tr>
<tr>
<td>SIGINT</td>
<td>2</td>
<td>程序终止(interrupt)信号，在用户键入 Ctrl+C 时发出。</td>
<td align="left">终止进程</td>
</tr>
<tr>
<td>SIGQUIT</td>
<td>3</td>
<td>和SIGINT类似，但由QUIT字符(通常是Ctrl &#x2F;)来控制。</td>
<td align="left">终止进程并dump core</td>
</tr>
<tr>
<td>SIGFPE</td>
<td>8</td>
<td>在发生致命的算术运算错误时发出。不仅包括浮点运算错误，还包括溢出及除数为0等其它所有的算术错误。</td>
<td align="left">终止进程并dump core</td>
</tr>
<tr>
<td>SIGKILL</td>
<td>9</td>
<td>用来立即结束程序的运行。本信号不能被阻塞，处理和忽略。</td>
<td align="left">终止进程</td>
</tr>
<tr>
<td>SIGALRM</td>
<td>14</td>
<td>时钟定时信号，计算的是实际的时间或时钟时间。alarm 函数使用该信号。</td>
<td align="left">终止进程</td>
</tr>
<tr>
<td>SIGTERM</td>
<td>15</td>
<td>通常用来要求程序自己正常退出；kill 命令缺省产生这个信号。</td>
<td align="left">终止进程</td>
</tr>
</tbody></table>
<h2 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h2><p>下面以 supervisor 为例，Dockerfile 如下</p>
<figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> centos:centos7</span><br><span class="line"><span class="keyword">ENV</span> PYTHONUNBUFFERED=<span class="number">1</span> TZ=Asia/Shanghai</span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> yum -y install epel-release &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">    yum -y install supervisor &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">    yum -y clean all  &amp;&amp; <span class="built_in">rm</span> -rf /var/cache</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> ./ /root/</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [ <span class="string">&quot;/usr/bin/supervisord&quot;</span>, <span class="string">&quot;-n&quot;</span>, <span class="string">&quot;-c&quot;</span>, <span class="string">&quot;/etc/supervisord.conf&quot;</span> ]</span></span><br></pre></td></tr></table></figure>

<h2 id="trap"><a href="#trap" class="headerlink" title="trap"></a>trap</h2><p>正常情况，容器退出时supervisor启动的其他程序并不会收到 <code>SIGTERM</code> 信号，导致子程序直接退出了。</p>
<p>这里使用 <code>trap</code> 对程序的异常处理进行包装</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">trap</span> &lt;siginal handler&gt; &lt;signal 1&gt; &lt;signal 2&gt; ...</span><br></pre></td></tr></table></figure>

<p>新建一个初始化脚本，<code>init.sh</code></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line"></span><br><span class="line">/usr/bin/supervisord -n -c /etc/supervisord.conf &amp;</span><br><span class="line"></span><br><span class="line"><span class="built_in">trap</span> <span class="string">&quot;supervisorctl stop all &amp;&amp; sleep 3&quot;</span> TERM INT</span><br><span class="line"></span><br><span class="line"><span class="built_in">wait</span></span><br></pre></td></tr></table></figure>

<p>修改 ENTRYPOINT 为如下</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line">ENTRYPOINT <span class="selector-attr">[<span class="string">&quot;sh&quot;</span>, <span class="string">&quot;/root/init.sh&quot;</span>]</span></span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://www.ctl.io/developers/blog/post/gracefully-stopping-docker-containers/">https://www.ctl.io/developers/blog/post/gracefully-stopping-docker-containers/</a></li>
<li><a href="https://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html">https://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html</a></li>
<li><a href="https://wangchujiang.com/linux-command/c/trap.html">https://wangchujiang.com/linux-command/c/trap.html</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Linux</tag>
        <tag>Docker</tag>
      </tags>
  </entry>
  <entry>
    <title>hexo 站内搜索内容不完全问题修复</title>
    <url>/2021/10/11/hexo-local-search-not-complete-fix/</url>
    <content><![CDATA[<p>在使用 Hexo 的站内搜索时，发现搜索的内容不全。单步调试发现xml解析不完整，有部分内容被截断了。</p>
<p>在浏览器中打开<a href="/search.xml">&#x2F;search.xml</a>发现以下错误。显然xml中有非法字符，xml解析产生了错误。</p>
<span id="more"></span>
<p><img data-src="https://image.ponder.work/mweb/2021-10-12-16339770374988.jpg"></p>
<p>将search.xml文件保存，并用python打开，找到具体出错的位置。<br>utf8解码之后可以发现<code>\x10</code>非法字符，将其删除，重新生成文章问题解决。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>xxx = <span class="built_in">open</span>(<span class="string">&#x27;./tmp.xml&#x27;</span>, <span class="string">&#x27;rb&#x27;</span>).read()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>xxx.index(<span class="string">b&#x27;\x10\xE7\x84\xB6&#x27;</span>)</span><br><span class="line"><span class="number">923278</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>xxx[<span class="number">923278</span>:<span class="number">923278</span>+<span class="number">31</span>]</span><br><span class="line"><span class="string">b&#x27;\x10\xe7\x84\xb6\xe5\x88\x99\xef\xbc\x8c\xe4\xbb\x8a\xe4\xb9\x8b\xe4\xb8\x96\xe4\xba\xba\xef\xbc\x8c\xe7\xb1\xbb\xe6\xad\xa4&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>xxx[<span class="number">923278</span>:<span class="number">923278</span>+<span class="number">31</span>].decode()</span><br><span class="line"><span class="string">&#x27;\x10然则，今之世人，类此&#x27;</span></span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/2021-10-12-16339793845815.jpg"></p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>XML</tag>
      </tags>
  </entry>
  <entry>
    <title>Hexo 版本更新与技术债务</title>
    <url>/2026/01/02/hexo-upgrade-and-tech-debt/</url>
    <content><![CDATA[<p>昨天花了些时间将 Hexo + next 的博客版本更新了下，发现不少问题。<br>博客已经很多年了，一直用的是最开始的Hexo版本，nodejs 也是 v12，next 主题也是早年源码安装的，还在上面做了修改。<br>也是早就明白需要更新了，但是觉得麻烦，就一直没动，最后变得相当麻烦。</p>
<span id="more"></span>

<h2 id="依赖更新"><a href="#依赖更新" class="headerlink" title="依赖更新"></a>依赖更新</h2><p>首先是版本升级，先将 nodejs 切换到最新 lts（v22）。<br>再尝试使用npm将 package.json 中的库自动更新，但由于版本差距太大以及很多库不维护了，更新不了。</p>
<p>最后只能使用 hexo init 新建个博客项目，将里面的依赖项复制到老博客里，删除对应的老依赖。<br>剩下的依赖，大部分是安装的插件，只能一个个搜索用途，看看有没有替代或者还要不要用。<br>package.json 中的其他参数，参考最新的配置文件，将无用（过时）的设置项删除。</p>
<p>使用 npm 安装 next 主题，将原 themes&#x2F;next 中的配置文件复制到 _config.next.yml。<br>对比 hexo 和 next 官方 _config 文件，相应修改 _config.yml 和 _config.next.yml。<br>删除 themes 中重复的主题目录。</p>
<p>使用 npm install 安装依赖，保证能够正常生成博客。</p>
<p>删除 node_modules，改用 pnpm 管理依赖，更轻量快速。<br>使用 pnpm 时可能会报错，如某个库的 <code>index.node</code> 找不到，是因为 pnpm 默认不执行库的 post install 脚本。<br>需要将这些库添加到 pnpm.onlyBuiltDependencies 中。</p>
<figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line"><span class="string">&quot;pnpm&quot;</span>: &#123;</span><br><span class="line">  <span class="string">&quot;onlyBuiltDependencies&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;hexo-word-counter&quot;</span>,</span><br><span class="line">    <span class="string">&quot;hexo-util&quot;</span></span><br><span class="line">  ]</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>ps: </p>
<ul>
<li>总体来说 pnpm 还是存在一些兼容性问题，但大部分都不是 pnpm 本身的问题，基本上都是库的影子依赖。</li>
<li>大模型在这种疑难杂症上帮助很大</li>
</ul>
<h2 id="额外设置"><a href="#额外设置" class="headerlink" title="额外设置"></a>额外设置</h2><h3 id="hexo"><a href="#hexo" class="headerlink" title="hexo"></a>hexo</h3><p>修改 scaffolds 文章模板文件<br>老版本 hexo 的 scaffolds&#x2F;page.md 等文件格式与新版不同，会导致一些工具不能识别 front-matter, 需要更新。</p>
<p>老版本 front-matter 前面少了开头的 <code>---</code>，导致 vscode 的 hexo-util 插件不能识别 tag 等信息，调试了很久。</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 老版本</span></span><br><span class="line"><span class="attr">title:</span> &#123;&#123; <span class="string">title</span> &#125;&#125;</span><br><span class="line"><span class="attr">date:</span> &#123;&#123; <span class="string">date</span> &#125;&#125;</span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="comment"># 新版本</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">title:</span> &#123;&#123; <span class="string">title</span> &#125;&#125;</span><br><span class="line"><span class="attr">date:</span> &#123;&#123; <span class="string">date</span> &#125;&#125;</span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure>

<h3 id="next"><a href="#next" class="headerlink" title="next"></a>next</h3><p>自定义样式和脚本，之前是通过之间修改主题源码实现，现在使用 custom_file_path 注入自定义内容。<br>需要修改 _config.next.yml 文件</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">custom_file_path:</span></span><br><span class="line">  <span class="attr">head:</span> <span class="string">source/_data/head.njk</span></span><br><span class="line">  <span class="attr">bodyEnd:</span> <span class="string">source/_data/body-end.njk</span></span><br><span class="line">  <span class="attr">style:</span> <span class="string">source/_data/styles.styl</span></span><br></pre></td></tr></table></figure>

<h4 id="自定义样式"><a href="#自定义样式" class="headerlink" title="自定义样式"></a>自定义样式</h4><p>next 主题的 Muse scheme 整体比较符合我的口味，但部分样式还需要微调。</p>
<p>source&#x2F;_data&#x2F;styles.styl</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">// 自动给文章的 toc 和正文标题编号, 不对首页编号</span><br><span class="line"><span class="selector-tag">body</span> &#123;<span class="attribute">counter-reset</span>: h1&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h2</span> &#123;<span class="attribute">counter-reset</span>: h2&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h3</span> &#123;<span class="attribute">counter-reset</span>: h3&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h4</span> &#123;<span class="attribute">counter-reset</span>: h4&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h5</span> &#123;<span class="attribute">counter-reset</span>: h5&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h6</span> &#123;<span class="attribute">counter-reset</span>: h6&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h2</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h1;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;. &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h3</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h2;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h2) <span class="string">&quot; &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h4</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h3;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h2) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h3) <span class="string">&quot; &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h5</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h4;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h2) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h3) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h4) <span class="string">&quot; &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h6</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h5;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h2) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h3) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h4) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h5) <span class="string">&quot; &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h1</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h2</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h3</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h4</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h5</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h6</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">  <span class="attribute">counter-increment</span>: none</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 隐藏文章 toc 中文章目录 tab 中的友链信息，站点概览中的保留</span><br><span class="line"><span class="selector-class">.sidebar</span> <span class="selector-class">.sidebar-toc-active</span> + <span class="selector-class">.sidebar-blogroll</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: none</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h4 id="只在文章页显示侧边栏"><a href="#只在文章页显示侧边栏" class="headerlink" title="只在文章页显示侧边栏"></a>只在文章页显示侧边栏</h4><p>next 主题的侧边栏逻辑有点问题， 就算设置 sidebar.display 为 post，当从文章跳到其他页面时，侧边栏并不会自动关闭，所以只能通过脚本来绕过。</p>
<p>source&#x2F;_data&#x2F;body-end.njk </p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&lt;script&gt;</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;pjax:success&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable constant_">CONFIG</span>.<span class="property">sidebar</span>.<span class="property">display</span> !== <span class="string">&#x27;remove&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> hasTOC = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.post-toc:not(.placeholder-toc)&#x27;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 当没有 TOC 且侧边栏正在显示时，隐藏侧边栏</span></span><br><span class="line">      <span class="keyword">if</span> (!hasTOC &amp;&amp; <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">classList</span>.<span class="title function_">contains</span>(<span class="string">&#x27;sidebar-active&#x27;</span>)) &#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="title function_">dispatchEvent</span>(<span class="keyword">new</span> <span class="title class_">Event</span>(<span class="string">&#x27;sidebar:hide&#x27;</span>));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h4 id="自定义重定向"><a href="#自定义重定向" class="headerlink" title="自定义重定向"></a>自定义重定向</h4><p>hexo 的文章不支持多个入口，比如我想要分类中英文都可以访问，默认情况是做不到的。</p>
<p>在设置了 category_map 的情况下，hexo 只会生成 programming 等英文链接，无法同时保留中文url作为入口。</p>
<figure class="highlight nestedtext"><table><tr><td class="code"><pre><span class="line"><span class="attribute">category_map</span><span class="punctuation">:</span></span><br><span class="line">  <span class="attribute">编程</span><span class="punctuation">:</span> <span class="string">programming</span></span><br><span class="line">  <span class="attribute">随笔</span><span class="punctuation">:</span> <span class="string">writing</span></span><br><span class="line">  <span class="attribute">阅读</span><span class="punctuation">:</span> <span class="string">reading</span></span><br><span class="line">  <span class="attribute">工作生活</span><span class="punctuation">:</span> <span class="string">life</span></span><br></pre></td></tr></table></figure>

<p>所以最终通过在 404 页面添加自定义的 js 实现重定向逻辑。</p>
<p>source&#x2F;_data&#x2F;head.njk</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;% <span class="keyword">if</span> page.<span class="property">title</span> === <span class="string">&#x27;404 Page Not Found&#x27;</span> or page.<span class="property">type</span> === <span class="string">&#x27;404&#x27;</span> or page.<span class="property">layout</span> === <span class="string">&#x27;404&#x27;</span> %&#125;</span><br><span class="line">&lt;script&gt;</span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 从 Hexo 配置中获取 category_map</span></span><br><span class="line">  <span class="keyword">var</span> categoryMap = &#123;&#123; config.<span class="property">category_map</span> | dump | safe &#125;&#125;;</span><br><span class="line">  <span class="keyword">var</span> currentPath = <span class="built_in">decodeURIComponent</span>(<span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">pathname</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 检查路径中是否包含分类映射的key</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> categoryMap) &#123;</span><br><span class="line">    <span class="keyword">if</span> (currentPath.<span class="title function_">includes</span>(key)) &#123;</span><br><span class="line">      <span class="keyword">var</span> targetCategory = categoryMap[key];</span><br><span class="line">      <span class="keyword">var</span> targetPath = currentPath.<span class="title function_">replace</span>(key, targetCategory);</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 执行重定向</span></span><br><span class="line">      <span class="keyword">if</span> (currentPath !== targetPath) &#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">location</span>.<span class="title function_">replace</span>(targetPath);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)();</span><br><span class="line">&lt;/script&gt;</span><br><span class="line">&#123;% endif %&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h4 id="更换-gitalk-为-utterances"><a href="#更换-gitalk-为-utterances" class="headerlink" title="更换 gitalk 为 utterances"></a>更换 gitalk 为 utterances</h4><p>原先一直使用 gitalk 作为文章的评论系统。<br>gitalk 每个新文章都需要作者登陆触发新建评论 issue，不太方便，所以现在换成 utterances。</p>
<p>utterances 评论插件 next 主题已内置，修改 _config.next.yml 可以开启。</p>
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 注意先关闭 gitalk</span></span><br><span class="line"><span class="attr">utterances:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">repo:</span> <span class="string">ruanimal/ruanimal.github.io</span> <span class="comment"># Github repository owner and name</span></span><br><span class="line">  <span class="comment"># Available values: pathname | url | title | og:title</span></span><br><span class="line">  <span class="attr">issue_term:</span> <span class="string">title</span></span><br></pre></td></tr></table></figure>

<p>还需要在 github pages 仓库安装 utterances 应用。</p>
<p>自定义的域名可能不被 utterances 支持，无法登陆跳转<br>需要新建 source&#x2F;utterances.json</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;origins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;https://blog.ponder.work&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;https://ruanimal.github.io&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>如果你和我一样，博客有多个域名，副域名登陆跳转会失效，可以通过注入js修正<br>utterances 的登陆跳转是根据 canonical link，我们只要让它和当前url保持一致就行了 </p>
<p>在 source&#x2F;_data&#x2F;head.njk 文件添加以下内容</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&lt;script&gt;</span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> canonical = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;head &gt; link[rel=&quot;canonical&quot;]&#x27;</span>);</span><br><span class="line">  <span class="keyword">if</span> (canonical &amp;&amp; canonical.<span class="property">href</span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">var</span> oldUrl = <span class="keyword">new</span> <span class="title function_">URL</span>(canonical.<span class="property">href</span>);</span><br><span class="line">      canonical.<span class="property">href</span> = <span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">origin</span> + oldUrl.<span class="property">pathname</span> + oldUrl.<span class="property">search</span> + oldUrl.<span class="property">hash</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;Failed to update canonical link:&#x27;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)();</span><br><span class="line">&lt;/script&gt;</span><br></pre></td></tr></table></figure>


<h2 id="更换编辑器"><a href="#更换编辑器" class="headerlink" title="更换编辑器"></a>更换编辑器</h2><p>之前一直是用 mac 平台是 mweb 作为 hexo 的文章编辑器，实话说也非常好用。<br>但是我把主力操作系统换成 linux 之后，linux 上一直没找到与 mweb 相当的应用。<br>最终经过一番折腾，最终使用 vscode + Hexo Utils 插件作为平替。<br>同时对该插件进行来<a href="https://github.com/0x-jerry/vscode-hexo-utils/pulls/ruanimal">二开</a>，最终体验算是追平了 mweb。</p>
<h2 id="感受"><a href="#感受" class="headerlink" title="感受"></a>感受</h2><p>所谓技术债务，项目正常迭代的过程中是不断积累的，如果平时不跟进解决，最终在某个阶段就会爆炸，造成巨大影响。</p>
<p>平时迭代时，如果时间安排的过紧，我们就会倾向于采取保守的决策（能跑就不要动），最终不可避免的技术债务堆积。</p>
<p>当然，生活中其实也是这样吧，很多不紧急的问题，一直拖着，最终大概率也会无可挽回。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Hexo</tag>
        <tag>随感</tag>
      </tags>
  </entry>
  <entry>
    <title>HomeBrew 与无 root 权限 Linux 环境包管理</title>
    <url>/2023/05/28/homebrew-as-non-root-package-manager/</url>
    <content><![CDATA[<p>一些公用的 Linux 服务器，处于维护以及安全考虑，一般只会提供普通权限用户给使用者。<br>普通用户的权限满足日常使用是够了，但是难以配置自己的开发环境，安装一些自己需要的包。</p>
<p>如果都从源码编译安装软件，依赖的维护过于复杂，初始编译工具链的版本可能也不满足需求，如 gcc 版本过低。<br>如果申请 sudo 权限或者请求更新系统或安装 docker，后期责任难以界定，运维和管理员一般也不会同意。</p>
<p>所以，最优方案还是有需求的用户在个人目录维护自己的工具链和环境。下文方案为围绕 HomeBrew 构建。</p>
<span id="more"></span>

<h2 id="安装-miniconda-解决前置依赖"><a href="#安装-miniconda-解决前置依赖" class="headerlink" title="安装 miniconda 解决前置依赖"></a>安装 miniconda 解决前置依赖</h2><p>如果你的系统比较新，可以直接尝试<code>安装 HomeBrew</code>。</p>
<p>基于上面讨论的内容，公用服务器一般存在系统版本低的问题，是 centos7 或者 centos6 也毫不稀奇，而且如 glibc 等库的版本也非常低。</p>
<p>安装 HomeBrew 有两个强依赖，git 及 curl，而且依赖的版本都比较高，centos7 的版本也不能满足。<br>另外，由于 Brew 不少软件都需要从源码编译，gcc 和良好的网络环境也不可缺少。</p>
<p>幸好 miniconda 能够解决以上几点问题。miniconda 只是提供 HomeBrew 安装的依赖，后续可以删除。</p>
<p>配置 conda 源（可选）： 新建 <code>.condarc</code>，包含以下内容</p>
<figure class="highlight less"><table><tr><td class="code"><pre><span class="line">channels:</span><br><span class="line">  - defaults</span><br><span class="line">show_channel_urls: true</span><br><span class="line">default_channels:</span><br><span class="line">  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main</span><br><span class="line">  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/<span class="attribute">r</span></span><br><span class="line"><span class="attribute">  - https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2</span></span><br><span class="line"><span class="attribute">custom_channels</span>:</span><br><span class="line">  <span class="attribute">conda-forge</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">msys2</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">bioconda</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">menpo</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">pytorch</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">pytorch-lts</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">simpleitk</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br></pre></td></tr></table></figure>

<p>下载及安装 miniconda</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">下载</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">如果服务器的内置证书已过期， 增加 --no-check-certificate 条件跳过证书验证</span></span><br><span class="line">wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh &amp;&amp; chmod +x Miniconda3-latest-Linux-x86_64.sh</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装</span></span><br><span class="line">./Miniconda3-latest-Linux-x86_64.sh -b -p ~/miniconda3</span><br><span class="line">source ~/miniconda3/etc/profile.d/conda.sh</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装所需包</span></span><br><span class="line">conda install -y gcc_linux-64 gxx_linux-64 curl git</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">链接 gcc，等 HomeBrew 安装完成这些链接可以删掉</span></span><br><span class="line">cd ~/miniconda3/bin/</span><br><span class="line">ln -s x86_64-conda_cos6-linux-gnu-gcc gcc</span><br><span class="line">ln -s x86_64-conda_cos6-linux-gnu-cpp c++</span><br><span class="line">ln -s gcc cc</span><br></pre></td></tr></table></figure>

<h2 id="配置安装环境变量"><a href="#配置安装环境变量" class="headerlink" title="配置安装环境变量"></a>配置安装环境变量</h2><p>这些环境变量也可以配置到 bashrc 等文件，使之永久生效</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置 curl 和 git，可选</span></span><br><span class="line">export HOMEBREW_CURL_PATH=~/miniconda3/bin/curl</span><br><span class="line">export HOMEBREW_GIT_PATH=~/miniconda3/bin/git</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置安装源为清华源，如果网络畅通可忽略，清华源也可能403</span></span><br><span class="line">export HOMEBREW_INSTALL_FROM_API=1</span><br><span class="line">export HOMEBREW_API_DOMAIN=&quot;https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api&quot;</span><br><span class="line">export HOMEBREW_BOTTLE_DOMAIN=&quot;https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles&quot;</span><br><span class="line">export HOMEBREW_BREW_GIT_REMOTE=&quot;https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git&quot;</span><br><span class="line">export HOMEBREW_CORE_GIT_REMOTE=&quot;https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git&quot;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h2 id="安装-HomeBrew"><a href="#安装-HomeBrew" class="headerlink" title="安装 HomeBrew"></a>安装 HomeBrew</h2><p>由于没有 root 权限，HomeBrew 需要手动安装。<br>由于是手动安装，位置与默认安装位置不同，很多预编译的包就不能用了，都得从源码编译，所以网络和机器性能以及耐心很重要。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">下载，也可使用清华源 https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git</span></span><br><span class="line">git clone https://github.com/Homebrew/brew ~/.homebrew</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装</span></span><br><span class="line">eval &quot;$(~/.homebrew/bin/brew shellenv)&quot;</span><br><span class="line">brew update --force --quiet</span><br><span class="line">chmod -R go-w &quot;$(brew --prefix)/share/zsh&quot;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">自动加载 brew</span></span><br><span class="line">echo &#x27;eval &quot;$(~/.homebrew/bin/brew shellenv)&quot;&#x27; &gt;&gt; ~/.bashrc</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://docs.brew.sh/Installation">https://docs.brew.sh/Installation</a></li>
<li><a href="https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/">https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/</a></li>
<li><a href="https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/">https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/</a></li>
<li><a href="https://github.com/tuna/issues/issues/1353">https://github.com/tuna/issues/issues/1353</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Linux</tag>
      </tags>
  </entry>
  <entry>
    <title>IEEE 754 与浮点数的二进制表示</title>
    <url>/2020/08/03/ieee754-floating-point-representation/</url>
    <content><![CDATA[<p>在计算机科学中，浮点（英语：floating point，缩写为FP）是一种对于实数的近似值数值表现法, 类似于十进制的<a href="https://zh.wikipedia.org/wiki/%E7%A7%91%E5%AD%A6%E8%AE%B0%E6%95%B0%E6%B3%95">科学计数法</a>.</p>
<span id="more"></span>
<h2 id="科学记数法"><a href="#科学记数法" class="headerlink" title="科学记数法"></a>科学记数法</h2><p>在科学记数法中，一个数被写成一个”实数”与一个10的<code>n</code>次幂的积<br>$$\pm a \times 10 ^ n$$<br>其中：</p>
<ul>
<li>$n$ 必须是一个整数, 可称之为指数。</li>
<li>$a$ 必须是<code>[1, 10)</code>区间内的实数，可称为有效数或尾数。</li>
</ul>
<p>类似的，二进制的科学计数法则是 $\pm a \times 2 ^ n$ ，不同的是 $a$ 必须是<code>[1, 2)</code>区间内的实数。</p>
<p>所以浮点数的二进制表示，就是用二进制位表示为 $\pm a \times 2 ^ n$ 。</p>
<h2 id="IEEE-754浮点数表示"><a href="#IEEE-754浮点数表示" class="headerlink" title="IEEE 754浮点数表示"></a>IEEE 754浮点数表示</h2><p>所以，我们可以将一定长度的二进制位分成三个部分，用来分别表示 $\pm$、$n$、$a$ ，而IEEE 754就是具体实现的标准。</p>
<p>IEEE二进制浮点数算术标准（IEEE 754）是20世纪80年代以来最广泛使用的浮点数运算标准，为许多CPU与浮点运算器所采用。</p>
<p>IEEE 754规定，对于32位的浮点数，最高的1位表示符号 $\pm$ 记为<code>s</code>(sign)，接着的8位表示指数 $n$ 记为<code>E</code>(exponent)，剩下的23位为有效数 $a$ 记为<code>M</code>(fraction)。</p>
<p>8位二进制位，能表示的256个数值，由于指数有正有负，标准规定从-127开始计数，也就是-127到128（与有符号数的实现不同），而且-127和128被用作特殊值处理，见下方<a href="#%E7%89%B9%E6%AE%8A%E5%80%BC">特殊值</a>。</p>
<p>同时，由于M的整数部分永远是1，我们可以只表示其小数部分，记为<code>N</code>。</p>
<p>也就是最终可表示为 $s \times 2 ^ E \times (1+N)$ 。</p>
<p>具体各部分拆解如下，其中 $a_0$ 到 $a_{31}$ 对应32个二进制位的值，为<code>0</code>或者<code>1</code>。</p>
<p>$$s &#x3D; (-1)^{a_{0}}$$<br>$$E &#x3D; -127 + a_{1}\times 2^{7} + a_{2}\times 2^{6} + \dots + a_{8}\times2^0$$<br>$$N &#x3D; a_{9}\times 2^{-1} + a_{10}\times 2^{-2} + \dots + a_{31}\times2^{-23}$$</p>
<p>以十进制的 $-5.0$ 为例，可表示为 $-1.25 \times 2 ^ 2$。那么，s&#x3D;1，N&#x3D;0.25，E&#x3D;2。<br>具体来说<br>$$s&#x3D;(-1)^1&#x3D;-1$$<br>$$E&#x3D;-127 + 2^7 + 2^0 &#x3D; 2$$<br>$$N&#x3D;2^{-2}&#x3D;0.25$$</p>
<p><img data-src="https://image.ponder.work/mweb/2020-08-04-15965370742818.jpg"></p>
<h2 id="特殊值"><a href="#特殊值" class="headerlink" title="特殊值"></a>特殊值</h2><p>这里有三个特殊值需要指出：</p>
<ul>
<li>如果指数是0并且尾数的小数部分是0，这个数±0（和符号位相关）</li>
<li>如果指数的二进制位<code>全为1</code>并且尾数的小数部分是0，这个数是±∞（同样和符号位相关）</li>
<li>如果指数的二进制位<code>全为1</code>并且尾数的小数部分非0，这个数表示为非数（NaN）。</li>
</ul>
<p>单精度浮点数各种极值情况：</p>
<table>
<thead>
<tr>
<th>类别</th>
<th>正负号</th>
<th>实际指数</th>
<th>有偏移指数</th>
<th>指数域</th>
<th>尾数域</th>
<th>数值</th>
</tr>
</thead>
<tbody><tr>
<td>零</td>
<td>0</td>
<td>-127</td>
<td>0</td>
<td>0000 0000</td>
<td>000 0000 0000 0000 0000 0000</td>
<td>0.0</td>
</tr>
<tr>
<td>负零</td>
<td>1</td>
<td>-127</td>
<td>0</td>
<td>0000 0000</td>
<td>000 0000 0000 0000 0000 0000</td>
<td>−0.0</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>0</td>
<td>127</td>
<td>0111 1111</td>
<td>000 0000 0000 0000 0000 0000</td>
<td>1.0</td>
</tr>
<tr>
<td>-1</td>
<td>1</td>
<td>0</td>
<td>127</td>
<td>0111 1111</td>
<td>000 0000 0000 0000 0000 0000</td>
<td>−1.0</td>
</tr>
<tr>
<td>最小的非规约数</td>
<td>*</td>
<td>-126</td>
<td>0</td>
<td>0000 0000</td>
<td>000 0000 0000 0000 0000 0001</td>
<td>±2−23 × 2−126 &#x3D; ±2−149 ≈ ±1.4×10-45</td>
</tr>
<tr>
<td>中间大小的非规约数</td>
<td>*</td>
<td>-126</td>
<td>0</td>
<td>0000 0000</td>
<td>100 0000 0000 0000 0000 0000</td>
<td>±2−1 × 2−126 &#x3D; ±2−127 ≈ ±5.88×10-39</td>
</tr>
<tr>
<td>最大的非规约数</td>
<td>*</td>
<td>-126</td>
<td>0</td>
<td>0000 0000</td>
<td>111 1111 1111 1111 1111 1111</td>
<td>±(1−2−23) × 2−126 ≈ ±1.18×10-38</td>
</tr>
<tr>
<td>最小的规约数</td>
<td>*</td>
<td>-126</td>
<td>1</td>
<td>0000 0001</td>
<td>000 0000 0000 0000 0000 0000</td>
<td>±2−126 ≈ ±1.18×10-38</td>
</tr>
<tr>
<td>最大的规约数</td>
<td>*</td>
<td>127</td>
<td>254</td>
<td>1111 1110</td>
<td>111 1111 1111 1111 1111 1111</td>
<td>±(2−2−23) × 2127 ≈ ±3.4×1038</td>
</tr>
<tr>
<td>正无穷</td>
<td>0</td>
<td>128</td>
<td>255</td>
<td>1111 1111</td>
<td>000 0000 0000 0000 0000 0000</td>
<td>+∞</td>
</tr>
<tr>
<td>负无穷</td>
<td>1</td>
<td>128</td>
<td>255</td>
<td>1111 1111</td>
<td>000 0000 0000 0000 0000 0000</td>
<td>−∞</td>
</tr>
<tr>
<td>NaN</td>
<td>*</td>
<td>128</td>
<td>255</td>
<td>1111 1111</td>
<td>non zero</td>
<td>NaN</td>
</tr>
<tr>
<td>* 符号位可以为0或1 .</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="浮点数与二进制字符串转换"><a href="#浮点数与二进制字符串转换" class="headerlink" title="浮点数与二进制字符串转换"></a>浮点数与二进制字符串转换</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">binary_to_float</span>(<span class="params">data</span>):</span><br><span class="line">    <span class="keyword">assert</span> <span class="built_in">len</span>(data) == <span class="number">32</span></span><br><span class="line">    sign = (-<span class="number">1</span>) ** <span class="built_in">int</span>(data[<span class="number">0</span>])</span><br><span class="line">    exponent = <span class="number">2</span> ** (-<span class="number">127</span> + <span class="built_in">sum</span>(<span class="built_in">int</span>(a) * <span class="number">2</span> ** b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(data[<span class="number">1</span>:<span class="number">9</span>], <span class="built_in">range</span>(<span class="number">7</span>, -<span class="number">1</span>, -<span class="number">1</span>))))</span><br><span class="line">    fraction = <span class="number">1</span> + <span class="built_in">sum</span>(<span class="built_in">int</span>(a) * <span class="number">2</span> ** b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(data[<span class="number">9</span>:], <span class="built_in">range</span>(-<span class="number">1</span>, -<span class="number">24</span>, -<span class="number">1</span>)))</span><br><span class="line">    <span class="keyword">return</span> sign * exponent * fraction</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">float_to_binary</span>(<span class="params">data</span>):</span><br><span class="line">    <span class="keyword">import</span> struct</span><br><span class="line">    bins = struct.pack(<span class="string">&#x27;&gt;f&#x27;</span>, data)</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>.join(<span class="string">&#x27;&#123;:0&gt;8&#125;&#x27;</span>.<span class="built_in">format</span>(<span class="built_in">bin</span>(i)[<span class="number">2</span>:]) <span class="keyword">for</span> i <span class="keyword">in</span> bins)</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://zh.wikipedia.org/wiki/IEEE_754">https://zh.wikipedia.org/wiki/IEEE_754</a></li>
<li><a href="https://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html">https://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html</a></li>
<li><a href="https://zh.wikipedia.org/wiki/%E7%A7%91%E5%AD%A6%E8%AE%B0%E6%95%B0%E6%B3%95">https://zh.wikipedia.org/wiki/%E7%A7%91%E5%AD%A6%E8%AE%B0%E6%95%B0%E6%B3%95</a></li>
<li>《深入理解计算机系统》</li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>浮点数</tag>
      </tags>
  </entry>
  <entry>
    <title>跨域的那些事</title>
    <url>/2023/08/15/cross-domain-things/</url>
    <content><![CDATA[<ul>
<li>什么是跨域？<ul>
<li>就是当前域访问了非本域的资源。对于http来说，url代表资源，也就是访问了非本域的url。</li>
</ul>
</li>
</ul>
<span id="more"></span>
<ul>
<li>域的定义是啥？<ul>
<li>这里的域，也就是同源策略（Same-origin policy）中的源。</li>
<li>如果两个 URL 的协议、端口和主机都相同的话，则这两个 URL 是同源的。</li>
<li>当前域：当前网页的域；目标域：访问的资源所在的域</li>
</ul>
</li>
<li>为什么要限制跨域请求？信息泄露<ul>
<li>js直接读取浏览器的目标域的sessionid、cookie，伪造请求。<ul>
<li>比如，当前站点是恶意站点，它用js请求银行站点，盗取信息。</li>
</ul>
</li>
<li>CSRF，如img标签的src会被访问，利用受害者已认证的身份，在不知情的情况下向受害者认证的站点发起恶意请求。<ul>
<li>通过在src里构造url，恶意请求目标域<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">&lt;img <span class="attribute">src</span>=<span class="string">&quot;https://bank.com/transfer?amount=1000&amp;to=attackerAccount&quot;</span> </span><br><span class="line">      <span class="attribute">style</span>=<span class="string">&quot;display:none;&quot;</span>&gt;</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
<li>XSS跨站脚本(Cross-site scripting)注入，导致用户在信息泄露<ul>
<li>一般是接受了用户构造的输入，输入里包含恶意脚本内容，内容未被转义，又输出在页面上，从而被执行</li>
<li>其他用户查看了该页面，注入的脚本被执行</li>
</ul>
</li>
</ul>
</li>
<li>怎么限制跨域？浏览器的同源策略<ul>
<li>禁止的<ul>
<li>DOM 同源策略： 不同源的dom之间不能相互操作，多个iframe的情况</li>
<li>XMLHttpRequest 同源策略： 禁止请求不同源url</li>
<li>Cookie、LocalStorage、IndexedDB 等存储性内容同源策略</li>
</ul>
</li>
<li>允许的<ul>
<li>页面中的链接，重定向以及表单提交</li>
<li><code>&lt;script&gt;、&lt;img&gt;、&lt;link&gt;</code>这些包含 src 属性的标签可以加载跨域资源。（只能GET）</li>
</ul>
</li>
</ul>
</li>
<li>限制跨域导致哪些不便？<ul>
<li>前后端分离开发时，localhost不能正常访问后端资源</li>
<li>一些公共的api不能被访问</li>
<li>https的页面的http的静态资源，不能加载</li>
</ul>
</li>
<li>如何绕过同源策略？<ul>
<li>浏览器启动参数（在用户端操作）</li>
<li>反向代理（在当前域操作）</li>
<li>JSONP（在目标域操作）<ul>
<li>利用<code>&lt;script&gt;</code>允许跨域的特点，设置标签的src为目标域，动态生成需要的javascript内容</li>
</ul>
</li>
<li>跨源资源共享（CORS）（目标域操作）<ul>
<li>设置相应的reponse header<figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">Access</span>-Control-Allow-Origin: https://foo.example  // 所允许的来源域</span><br><span class="line"><span class="keyword">Access</span>-Control-Allow-Methods: POST, <span class="keyword">GET</span>, <span class="keyword">OPTIONS</span>   // 所允许的请求方法</span><br><span class="line"><span class="keyword">Access</span>-Control-Allow-Headers: X-PINGOTHER, Content-<span class="keyword">Type</span>  // 所允许的请求<span class="keyword">header</span></span><br><span class="line"><span class="keyword">Access</span>-Control-Max-Age: <span class="number">86400</span></span><br></pre></td></tr></table></figure></li>
</ul>
</li>
</ul>
</li>
<li>参考<ul>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy">https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS">https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Glossary/CSRF">https://developer.mozilla.org/zh-CN/docs/Glossary/CSRF</a></li>
<li><a href="https://juejin.cn/post/6879360544323665928">https://juejin.cn/post/6879360544323665928</a></li>
<li><a href="https://juejin.cn/post/6867096987804794888">https://juejin.cn/post/6867096987804794888</a></li>
</ul>
</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Web</tag>
      </tags>
  </entry>
  <entry>
    <title>在WSL2中安装openmediavault(OMV)</title>
    <url>/2021/01/02/install-openmediavault-in-WSL2/</url>
    <content><![CDATA[<p><strong>本方案存在不可解决的缺点，已被笔者放弃，请移步<a href="/2021/09/21/openmediavault-on-hyperv/">最新解决方案</a></strong></p>
<p>NAS的文件系统一直是我比较纠结的一个点。NAS的系统基本上是基于Linux(Unix)，文件系统不是ntfs，数据迁移不方便，数据恢复工具也没那么全。</p>
<p>WSL就完美解决了这个问题，用Linux提供服务，数据最终还是落在ntfs上，而且重要的是everything也能用上。</p>
<p>openmediavault(OMV)是一个基于Debian的NAS系统，而且能在原生Debian系统上自行安装，正好能够实现我们的功能。</p>
<span id="more"></span>

<h2 id="WSL2-相关准备工作"><a href="#WSL2-相关准备工作" class="headerlink" title="WSL2 相关准备工作"></a>WSL2 相关准备工作</h2><p>需要安装WSL2并启用桥接网络，同时安装好Debian系统</p>
<p>参考本人<a href="/2021/01/02/WSL2-bridge-mode/">这篇文章</a></p>
<h2 id="安装-systemd"><a href="#安装-systemd" class="headerlink" title="安装 systemd"></a>安装 systemd</h2><p>由于openmediavault对systemd有强依赖，而WSL的系统默认是由<code>/init</code>启动的，会导致安装出错<br>所以得先把systemd的问题解决。</p>
<p>经过一番google，找到了<a href="https://github.com/arkane-systems/genie">systemd-genie</a>能够解决问题</p>
<h3 id="安装-dotnet-源"><a href="#安装-dotnet-源" class="headerlink" title="安装 dotnet 源"></a>安装 dotnet 源</h3><p>systemd-genie 依赖 dotnet-runtime-5.0, 所以把dotnet源配好，参考<a href="https://docs.microsoft.com/zh-cn/dotnet/core/install/linux-debian#debian-10-">文档</a></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb</span><br><span class="line"><span class="built_in">sudo</span> dpkg -i packages-microsoft-prod.deb</span><br><span class="line"><span class="built_in">sudo</span> apt-get update; \</span><br><span class="line">  <span class="built_in">sudo</span> apt-get install -y apt-transport-https &amp;&amp; \</span><br><span class="line">  <span class="built_in">sudo</span> apt-get update</span><br></pre></td></tr></table></figure>

<h3 id="安装-systemd-genie"><a href="#安装-systemd-genie" class="headerlink" title="安装 systemd-genie"></a>安装 systemd-genie</h3><p>官网提供了安装脚本, 执行完会配置好systemd-genie源并安装</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -s https://packagecloud.io/install/repositories/arkane-systems/wsl-translinux/script.deb.sh | <span class="built_in">sudo</span> bash</span><br></pre></td></tr></table></figure>

<p>但是安装过程中，发现systemd-genie源的访问有些问题，所以只能从gitbub下载安装</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> /tmp &amp;&amp; wget https://github.com/arkane-systems/genie/releases/download/1.30/systemd-genie_1.30_amd64.deb</span><br><span class="line">dpkg -i systemd-genie_1.30_amd64.deb</span><br><span class="line">apt --fix-broken install</span><br></pre></td></tr></table></figure>

<h3 id="默认启动-systemd-genie"><a href="#默认启动-systemd-genie" class="headerlink" title="默认启动 systemd-genie"></a>默认启动 systemd-genie</h3><p>修改<code>/root/.bashrc</code>, 添加以下内容</p>
<figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="string">[[ ! -v INSIDE_GENIE ]]</span>; <span class="keyword">then</span></span><br><span class="line">    echo <span class="string">&quot;Starting genie&quot;</span></span><br><span class="line">    exec /usr/bin/genie -s</span><br><span class="line">fi</span><br></pre></td></tr></table></figure>

<h2 id="网络相关配置"><a href="#网络相关配置" class="headerlink" title="网络相关配置"></a>网络相关配置</h2><p>主机名和网口配置重启WSL后可能会失效，建议修改配置文件来实现</p>
<h3 id="主机名"><a href="#主机名" class="headerlink" title="主机名"></a>主机名</h3><p>修改<code>/etc/wsl.conf</code>，增加<code>hostname = openmediavault</code></p>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[network]</span></span><br><span class="line"><span class="attr">hostname</span> = openmediavault</span><br><span class="line"><span class="attr">generateResolvConf</span> = <span class="literal">false</span></span><br></pre></td></tr></table></figure>

<p>修改<code>/etc/genie.ini</code>，设置<code>update-hostname</code>为false</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[genie]</span></span><br><span class="line"><span class="attr">secure-path</span>=/lib/systemd:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</span><br><span class="line"><span class="attr">unshare</span>=/usr/bin/unshare</span><br><span class="line"><span class="attr">update-hostname</span>=<span class="literal">false</span></span><br><span class="line"><span class="attr">clone-path</span>=<span class="literal">false</span></span><br><span class="line"><span class="attr">clone-env</span>=WSL_DISTRO_NAME,WSL_INTEROP,WSLENV</span><br></pre></td></tr></table></figure>

<h3 id="ip地址"><a href="#ip地址" class="headerlink" title="ip地址"></a>ip地址</h3><p>注意，由于WSL的mac地址每次重启都会变，该问题暂时无法解决，参考<a href="https://github.com/microsoft/WSL/issues/5352">issue</a><br>所以这里采用静态IPv4地址和DHCPv6来配置网络地址</p>
<p>新建<code>/etc/systemd/network/lan.network</code>，内容如下</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[Match]</span></span><br><span class="line"><span class="attr">Name</span>=eth0</span><br><span class="line"></span><br><span class="line"><span class="section">[Network]</span></span><br><span class="line"><span class="attr">Description</span>=lan</span><br><span class="line"><span class="attr">DHCP</span>=ipv6</span><br><span class="line"><span class="attr">Address</span>=<span class="number">192.168</span>.<span class="number">123.31</span>/<span class="number">24</span></span><br><span class="line"><span class="attr">Gateway</span>=<span class="number">192.168</span>.<span class="number">123.1</span></span><br><span class="line"><span class="attr">DNS</span>=<span class="number">192.168</span>.<span class="number">123.1</span></span><br><span class="line"><span class="attr">LLDP</span>=<span class="literal">true</span></span><br><span class="line"><span class="attr">EmitLLDP</span>=<span class="literal">true</span></span><br></pre></td></tr></table></figure>

<p>然后执行执行<code>systemctl enable systemd-networkd &amp;&amp; systemctl start systemd-networkd</code></p>
<h2 id="安装-openmediavault"><a href="#安装-openmediavault" class="headerlink" title="安装 openmediavault"></a>安装 openmediavault</h2><p>参考<a href="https://openmediavault.readthedocs.io/en/5.x/installation/on_debian.html">官方文档</a></p>
<p>由于要安装的包比较多，建议先把Debian软件源替换成国内镜像，参考<a href="https://mirrors.tuna.tsinghua.edu.cn/help/debian/">这个</a></p>
<ol>
<li><p>添加软件源keyring</p>
 <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">apt-get install --<span class="built_in">yes</span> gnupg</span><br><span class="line">wget -O <span class="string">&quot;/etc/apt/trusted.gpg.d/openmediavault-archive-keyring.asc&quot;</span> https://packages.openmediavault.org/public/archive.key</span><br><span class="line">apt-key add <span class="string">&quot;/etc/apt/trusted.gpg.d/openmediavault-archive-keyring.asc&quot;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>添加openmediavault软件源</p>
 <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF &gt;&gt; /etc/apt/sources.list.d/openmediavault.list</span></span><br><span class="line"><span class="string">deb https://packages.openmediavault.org/public usul main</span></span><br><span class="line"><span class="string"># deb https://downloads.sourceforge.net/project/openmediavault/packages usul main</span></span><br><span class="line"><span class="string">## Uncomment the following line to add software from the proposed repository.</span></span><br><span class="line"><span class="string"># deb https://packages.openmediavault.org/public usul-proposed main</span></span><br><span class="line"><span class="string"># deb https://downloads.sourceforge.net/project/openmediavault/packages usul-proposed main</span></span><br><span class="line"><span class="string">## This software is not part of OpenMediaVault, but is offered by third-party</span></span><br><span class="line"><span class="string">## developers as a service to OpenMediaVault users.</span></span><br><span class="line"><span class="string"># deb https://packages.openmediavault.org/public usul partner</span></span><br><span class="line"><span class="string"># deb https://downloads.sourceforge.net/project/openmediavault/packages usul partner</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>安装</p>
 <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> LANG=C.UTF-8</span><br><span class="line"><span class="built_in">export</span> DEBIAN_FRONTEND=noninteractive</span><br><span class="line"><span class="built_in">export</span> APT_LISTCHANGES_FRONTEND=none</span><br><span class="line">wget -O <span class="string">&quot;/etc/apt/trusted.gpg.d/openmediavault-archive-keyring.asc&quot;</span> https://packages.openmediavault.org/public/archive.key</span><br><span class="line">apt-key add <span class="string">&quot;/etc/apt/trusted.gpg.d/openmediavault-archive-keyring.asc&quot;</span></span><br><span class="line">apt-get update</span><br><span class="line">apt-get --<span class="built_in">yes</span> --auto-remove --show-upgraded \</span><br><span class="line">    --allow-downgrades --allow-change-held-packages \</span><br><span class="line">    --no-install-recommends \</span><br><span class="line">    --option Dpkg::Options::=<span class="string">&quot;--force-confdef&quot;</span> \</span><br><span class="line">    --option DPkg::Options::=<span class="string">&quot;--force-confold&quot;</span> \</span><br><span class="line">    install openmediavault-keyring openmediavault</span><br><span class="line"></span><br><span class="line">omv-confdbadm populate</span><br></pre></td></tr></table></figure></li>
</ol>
<h2 id="安装-omv-extras（可选）"><a href="#安装-omv-extras（可选）" class="headerlink" title="安装 omv-extras（可选）"></a>安装 omv-extras（可选）</h2><p>参考<a href="https://forum.openmediavault.org/index.php?thread/5549-omv-extras-org-plugin/">官方文档</a></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">wget -O - https://github.com/OpenMediaVault-Plugin-Developers/packages/raw/master/install | bash</span><br></pre></td></tr></table></figure>

<h2 id="配置-openmediavault"><a href="#配置-openmediavault" class="headerlink" title="配置 openmediavault"></a>配置 openmediavault</h2><h3 id="系统配置"><a href="#系统配置" class="headerlink" title="系统配置"></a>系统配置</h3><p>OMV里的主机名和网络配置就配置成和上面配置文件里的一样，防止OMV的服务工作异常</p>
<ol>
<li><p>修改主机名，防止WSL默认主机名过长（必须小于15位），导致samba配置失败<br> <img data-src="https://image.ponder.work/mweb/2021-01-02-2021-01-02_180911.png" alt="2021-01-02_180911"></p>
</li>
<li><p>打开<code>系统 -&gt; 网络 -&gt; 添加 -&gt; 以太网</code>，配置网口ip，这里配置为DHCP自动获取<br> <img data-src="https://image.ponder.work/mweb/2021-01-02-2021-01-02_181342.png" alt="2021-01-02_181342"></p>
</li>
<li><p>打开<code>系统 -&gt; 常规设置 -&gt; Web管理员密码</code>，修改管理员密码，默认密码为<code>admin:openmediavault</code></p>
</li>
</ol>
<h3 id="共享文件夹配置"><a href="#共享文件夹配置" class="headerlink" title="共享文件夹配置"></a>共享文件夹配置</h3><p>由于WSL2读写本机硬盘是使用的微软的驱动，OMV并不支持，默认只能识别到根目录的虚拟硬盘。</p>
<p>又因为WSL2的本地硬盘都挂载在<code>/mnt/</code>路径下，所以只要能将<code>/mnt/</code>目录共享就ok了</p>
<p>这里参考<a href="https://github.com/openmediavault/openmediavault/blob/master/deb/openmediavault-sharerootfs/debian/openmediavault-sharerootfs.postinst">openmediavault-sharerootfs</a>的实现</p>
<p>修改<code>/etc/openmediavault/config.xml</code>配置文件， 在<code>fstab</code>标签下增加<code>mntent</code>挂载点配置，修改完如下</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!--省略其他部分--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">fstab</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">mntent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">uuid</span>&gt;</span>79684322-3eac-11ea-a974-63a080abab18<span class="tag">&lt;/<span class="name">uuid</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">fsname</span>&gt;</span>/dev/sdb<span class="tag">&lt;/<span class="name">fsname</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dir</span>&gt;</span>/<span class="tag">&lt;/<span class="name">dir</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">type</span>&gt;</span>ext4<span class="tag">&lt;/<span class="name">type</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">opts</span>&gt;</span>errors=remount-ro<span class="tag">&lt;/<span class="name">opts</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">freq</span>&gt;</span>0<span class="tag">&lt;/<span class="name">freq</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">passno</span>&gt;</span>1<span class="tag">&lt;/<span class="name">passno</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">hidden</span>&gt;</span>1<span class="tag">&lt;/<span class="name">hidden</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">mntent</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">fstab</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>打开<code>访问权限管理 -&gt; 共享文件夹 -&gt; 添加</code>，添加D盘（&#x2F;mnt&#x2F;d）作为共享文件夹，然后在samba服务中就能引用这个共享文件夹了<br><img data-src="https://image.ponder.work/mweb/2021-01-02-2021-01-02_183349.png" alt="2021-01-02_183349"></p>
<p><img data-src="https://image.ponder.work/mweb/2021-01-02-2021-01-02_183046.png" alt="2021-01-02_183046"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://github.com/arkane-systems/genie">https://github.com/arkane-systems/genie</a></li>
<li><a href="https://docs.microsoft.com/zh-cn/dotnet/core/install/linux-debian#debian-10-">https://docs.microsoft.com/zh-cn/dotnet/core/install/linux-debian#debian-10-</a></li>
<li><a href="https://openmediavault.readthedocs.io/en/5.x/installation/on_debian.html">https://openmediavault.readthedocs.io/en/5.x/installation/on_debian.html</a></li>
<li><a href="https://forum.openmediavault.org/index.php?thread/5549-omv-extras-org-plugin/">https://forum.openmediavault.org/index.php?thread/5549-omv-extras-org-plugin/</a></li>
<li><a href="https://github.com/openmediavault/openmediavault/blob/master/deb/openmediavault-sharerootfs/debian/openmediavault-sharerootfs.postinst">https://github.com/openmediavault/openmediavault/blob/master/deb/openmediavault-sharerootfs/debian/openmediavault-sharerootfs.postinst</a></li>
</ol>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>Linux</tag>
        <tag>WSL</tag>
        <tag>NAS</tag>
      </tags>
  </entry>
  <entry>
    <title>IPython学习环境配置</title>
    <url>/2020/07/18/ipython-learning-environment-config/</url>
    <content><![CDATA[<h2 id="IPython简介"><a href="#IPython简介" class="headerlink" title="IPython简介"></a>IPython简介</h2><p>IPython项目起初是Fernando Pérez在2001年的一个用以加强和Python交互的子项目。在随后的16年中，它成为了Python数据栈最重要的工具之一。</p>
<p>简单来说，我们可以把IPython当成一个学习Python语言、数据分析、机器学习的平台。</p>
<span id="more"></span>

<h2 id="环境安装"><a href="#环境安装" class="headerlink" title="环境安装"></a>环境安装</h2><h3 id="Python"><a href="#Python" class="headerlink" title="Python"></a>Python</h3><p>这里我们使用<a href="https://docs.conda.io/en/latest/miniconda.html">miniconda</a>，防止机器学习和图形显示相关的库出问题. 使用pyenv的同学，也可以用pyenv来安装miniconda。</p>
<p>下载：<code>wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh</code></p>
<p>安装: <code>sh Miniconda3-latest-MacOSX-x86_64.sh</code></p>
<h3 id="创建ipython虚拟环境"><a href="#创建ipython虚拟环境" class="headerlink" title="创建ipython虚拟环境"></a>创建ipython虚拟环境</h3><ol>
<li>创建虚拟环境：<code>conda create -n ipython</code></li>
<li>激活环境：<code>conda activate ipython</code></li>
<li>安装必要的python库：<code>pip install ipython pandas ipykernel matplotlib</code></li>
</ol>
<h2 id="用Visual-Studio-Code作为编辑器"><a href="#用Visual-Studio-Code作为编辑器" class="headerlink" title="用Visual Studio Code作为编辑器"></a>用Visual Studio Code作为编辑器</h2><p><a href="https://code.visualstudio.com/">VSCode</a>在安装Python插件之后，就可以很方便得查看编辑IPython Notebook了</p>
<h3 id="安装Python插件"><a href="#安装Python插件" class="headerlink" title="安装Python插件"></a>安装Python插件</h3><p>在扩展商店，搜索“Python”， 并安装<br><img data-src="https://image.ponder.work/mweb/2020-07-19-15951449200011.jpg" alt="-w549"></p>
<h3 id="创建使用NoteBook"><a href="#创建使用NoteBook" class="headerlink" title="创建使用NoteBook"></a>创建使用NoteBook</h3><p>选择IPython环境<br><img data-src="https://image.ponder.work/mweb/2020-07-19-15951453546560.jpg" alt="-w1421"></p>
<p>创建<code>hello.ipynb</code>Notebook<br><img data-src="https://image.ponder.work/mweb/2020-07-19-15951457001314.jpg" alt="-w1421"></p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>数据分析</tag>
      </tags>
  </entry>
  <entry>
    <title>限流算法之漏桶与令牌桶</title>
    <url>/2021/05/30/leaky-bucket-and-token-bucket/</url>
    <content><![CDATA[<p>后端一个常见且比较让人头疼的问题是服务限流，没有做好限流开始导致单个服务耗时增加，进而影响上下游服务，最终可能导致整个系统被拖垮。</p>
<p>限流的目的是通过对并发请求进行限速来保护系统，一旦达到限制速率则可以拒绝服务、排队 或等待、降级。</p>
<span id="more"></span>
<p>一般使用的限流算法有漏桶（Leaky Bucket）和令牌桶(Token Bucket)。</p>
<p>这里有个需要注意的点，这两种算法的名称和示意图都是为了便于理解，实现时并不需要一模一样。</p>
<h2 id="漏桶算法"><a href="#漏桶算法" class="headerlink" title="漏桶算法"></a>漏桶算法</h2><p>漏桶作为计量工具（The Leaky Bucket Algorithm as a Meter )时，可以用于流量整形 (Traffic Shaping ) 和流量控制 （Traffic Policing )，漏桶算法的描述如下。</p>
<ul>
<li>一个固定容量的漏桶，按照常量固定速率流出水滴。</li>
<li>如果桶是空的， 则不需流出水滴。</li>
<li>可以以任意速率流入水滴到漏桶。</li>
<li>如果流入水滴超出了桶的容量， 则流入的水滴溢出了（被丢弃）。</li>
</ul>
<p><img data-src="https://image.ponder.work/mweb/2021-05-31-16223879750903.jpg" alt="-w962"></p>
<p>漏桶的关键在于漏出速度恒定，超出的流量会被丢弃，最终请求看起来会是这样，峰值完全被砍掉了，过于粗暴了，适用的场景不多。<br><img data-src="https://image.ponder.work/mweb/2021-05-31-16223889987592.jpg" alt="-w1308"></p>
<h3 id="漏桶Python实现"><a href="#漏桶Python实现" class="headerlink" title="漏桶Python实现"></a>漏桶Python实现</h3><p>网上常见的一种实现是用个队列直接存储请求来模拟漏桶，其实大可不必，内存空间占用大而且效率低。</p>
<p>其实只需要通过一个队列记录请求时间，结合漏桶漏出速率进行计算，然后移动窗口就可以实现漏桶了。</p>
<p>这里的实现对漏桶进行了简化，漏桶的容量恰好等于单位时间漏出的量。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> time <span class="keyword">import</span> time, sleep</span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> deque</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LeakyBucket</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, leaky_rate</span>):</span><br><span class="line">        <span class="variable language_">self</span>.leaky_rate = <span class="built_in">float</span>(leaky_rate)    <span class="comment"># 漏出速度</span></span><br><span class="line">        <span class="variable language_">self</span>._que = []   <span class="comment"># 请求时间队列</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">size</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">len</span>(<span class="variable language_">self</span>._que)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">inspect_expired_items</span>(<span class="params">self, time: <span class="built_in">int</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;检查已经漏出的请求</span></span><br><span class="line"><span class="string">        args:</span></span><br><span class="line"><span class="string">            time: 上一个时间窗口, 默认是秒</span></span><br><span class="line"><span class="string">        returns:</span></span><br><span class="line"><span class="string">            item_count: 已漏出的请求个数</span></span><br><span class="line"><span class="string">            remaining_time: 第一个未漏出请求的剩余时间</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        volume = <span class="variable language_">self</span>.size()</span><br><span class="line">        item_count, remaining_time = <span class="number">0</span>, <span class="number">0</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> log_idx, log_item <span class="keyword">in</span> <span class="built_in">enumerate</span>(<span class="variable language_">self</span>._que):</span><br><span class="line">            <span class="keyword">if</span> log_item &gt; time:</span><br><span class="line">                item_count = volume - log_idx</span><br><span class="line">                remaining_time = log_item - time</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">return</span> item_count, remaining_time</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">acquire</span>(<span class="params">self, block=<span class="literal">True</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        args:</span></span><br><span class="line"><span class="string">            block: 是否阻塞直到可以请求</span></span><br><span class="line"><span class="string">        returns:</span></span><br><span class="line"><span class="string">            - 是否可以请求</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        now = time()</span><br><span class="line">        volume = <span class="variable language_">self</span>.size()</span><br><span class="line">        <span class="keyword">if</span> volume &gt;= <span class="variable language_">self</span>.leaky_rate:   <span class="comment"># 容量已满需要清理已漏出请求</span></span><br><span class="line">            pre_tick = now - <span class="number">1</span>  <span class="comment"># 上一个时间窗口, 默认单位为秒</span></span><br><span class="line">            item_count, remaining_time = <span class="variable language_">self</span>.inspect_expired_items(pre_tick)</span><br><span class="line">            <span class="keyword">if</span> item_count &gt;= <span class="variable language_">self</span>.leaky_rate:   <span class="comment"># 上个周期请求已超限</span></span><br><span class="line">                <span class="keyword">if</span> block:</span><br><span class="line">                    sleep(remaining_time)   <span class="comment"># 等到出现一个空位</span></span><br><span class="line">                    <span class="built_in">print</span>(<span class="string">&#x27;Bucket Full, sleep &#123;&#125; seconds&#x27;</span>.<span class="built_in">format</span>(remaining_time))</span><br><span class="line">                    <span class="keyword">return</span> <span class="variable language_">self</span>.acquire()   <span class="comment"># 再次尝试</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">            <span class="variable language_">self</span>._que[:volume-item_count] = []    <span class="comment"># 清除上个周期数据</span></span><br><span class="line">        <span class="variable language_">self</span>._que.append(now)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    rate_limiter = LeakyBucket(<span class="number">2</span>)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">10</span>):</span><br><span class="line">        <span class="keyword">if</span> rate_limiter.acquire(block=<span class="literal">False</span>):</span><br><span class="line">            <span class="built_in">print</span>(time(), <span class="string">&#x27;succ&#x27;</span>, i)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="built_in">print</span>(time(), <span class="string">&#x27;skip&#x27;</span>, i)</span><br><span class="line">        sleep(<span class="number">0.2</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h2 id="令牌桶"><a href="#令牌桶" class="headerlink" title="令牌桶"></a>令牌桶</h2><p>令牌桶算法，是一个存放固定容量令牌的桶，按照固定速率往桶里添加令牌。</p>
<p>令牌桶算法的描述如下。</p>
<ul>
<li>假设限制2r&#x2F;s ，则按照500毫秒的固定速率往桶中添加令牌。</li>
<li>桶中最多存放b个令牌， 当桶满时，新添加的令牌被丢弃或拒绝。</li>
<li>当一个n个字节的数据包到达，将从桶中删除n个令牌，然后发送请求</li>
<li>如果桶中的令牌不足n个，则该数据包将被限流（要么丢弃， 要么在缓冲区等待）。</li>
</ul>
<p><img data-src="https://image.ponder.work/mweb/2021-05-31-16223906628083.jpg" alt="-w846"></p>
<p>由于令牌放置速度恒定，取出速度不限，所以令牌桶的限流是有一定弹性的，能够接受请求的一定波动。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-05-31-16223908269384.jpg" alt="-w1288"></p>
<h3 id="令牌桶Python实现"><a href="#令牌桶Python实现" class="headerlink" title="令牌桶Python实现"></a>令牌桶Python实现</h3><p>通过计算令牌桶容量和产生速率就可以实现令牌桶，并不需要真的实现“把令牌放桶里”和“取出令牌”。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> time <span class="keyword">import</span> time, sleep</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TokenBucket</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, tokens, fill_rate</span>):</span><br><span class="line">        <span class="variable language_">self</span>.capacity = <span class="built_in">float</span>(tokens)   <span class="comment"># 容量</span></span><br><span class="line">        <span class="variable language_">self</span>._tokens = <span class="built_in">float</span>(tokens)</span><br><span class="line">        <span class="variable language_">self</span>.fill_rate = <span class="built_in">float</span>(fill_rate)    <span class="comment"># 产生速度</span></span><br><span class="line">        <span class="variable language_">self</span>.timestamp = time()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">consume</span>(<span class="params">self, tokens, block=<span class="literal">True</span></span>):</span><br><span class="line">        <span class="keyword">assert</span> tokens &lt;= <span class="variable language_">self</span>.capacity, <span class="string">&#x27;Attempted to consume &#123;&#125; tokens from a bucket with capacity &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(tokens, <span class="variable language_">self</span>.capacity)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> block <span class="keyword">and</span> tokens &gt; <span class="variable language_">self</span>.tokens:</span><br><span class="line">            deficit = tokens - <span class="variable language_">self</span>._tokens</span><br><span class="line">            delay = deficit / <span class="variable language_">self</span>.fill_rate</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;Have &#123;&#125; tokens, need &#123;&#125;; sleeping &#123;&#125; seconds&#x27;</span>.<span class="built_in">format</span>(<span class="variable language_">self</span>._tokens, tokens, delay))</span><br><span class="line">            sleep(delay)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> tokens &lt;= <span class="variable language_">self</span>.tokens:</span><br><span class="line">            <span class="variable language_">self</span>._tokens -= tokens</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">tokens</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>._tokens &lt; <span class="variable language_">self</span>.capacity:</span><br><span class="line">            now = time()   <span class="comment"># 获取当前时间</span></span><br><span class="line">            delta = <span class="variable language_">self</span>.fill_rate * (now - <span class="variable language_">self</span>.timestamp)   <span class="comment"># 算出这段时间产出的令牌</span></span><br><span class="line">            <span class="variable language_">self</span>._tokens = <span class="built_in">min</span>(<span class="variable language_">self</span>.capacity, <span class="variable language_">self</span>._tokens + delta)   <span class="comment"># 丢弃超出容量的令牌</span></span><br><span class="line">            <span class="variable language_">self</span>.timestamp = now   <span class="comment"># 更新基准时间</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>._tokens</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">rate_limit</span>(<span class="params">data, bandwidth_or_burst, steady_state_bandwidth=<span class="literal">None</span></span>):</span><br><span class="line">    <span class="comment"># bandwidth_or_burst 令牌桶容量</span></span><br><span class="line">    <span class="comment"># steady_state_bandwidth 令牌产生速度</span></span><br><span class="line">    bandwidth = steady_state_bandwidth <span class="keyword">or</span> bandwidth_or_burst</span><br><span class="line">    rate_limiter = TokenBucket(bandwidth_or_burst, bandwidth)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> thing <span class="keyword">in</span> data:</span><br><span class="line">        rate_limiter.consume(<span class="built_in">len</span>(<span class="built_in">str</span>(thing)))</span><br><span class="line">        <span class="keyword">yield</span> thing</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    stream = rate_limit(<span class="built_in">range</span>(<span class="number">10</span>), <span class="number">5</span>, <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> stream:</span><br><span class="line">        <span class="built_in">print</span>(time(), i)</span><br></pre></td></tr></table></figure>

<p>注意：以上算法都是单机单线程的实现，如果需要多个机器限流则需要将桶的状态通过redis等外部服务来存储。</p>
<h2 id="令牌桶和漏桶算法对比"><a href="#令牌桶和漏桶算法对比" class="headerlink" title="令牌桶和漏桶算法对比"></a>令牌桶和漏桶算法对比</h2><ul>
<li>令牌桶是按照<strong>固定速率往桶中添加令牌</strong>，请求是否被处理需要看桶中令牌是否足够，当令牌数减为零时，则拒绝新的请求。</li>
<li>漏桶则是按照<strong>固定速率流出请求</strong>，流入请求速率任意，当流入的请求数累积到漏桶容量时，则新流入的请求被拒绝。</li>
<li>令牌桶限制的是平均流入速率（允许突发请求，只要有令牌就可以处理，支持一次拿3个令牌，或4个令牌）， 并允许一定程度的突发流量。</li>
<li>漏桶限制的是常量流出速率（即流出速率是一个固定常量值，比如都是1的速率流出，而不能一次是1，下次又是2) , 从而平滑突发流入速率。</li>
<li>令牌桶允许一定程度的突发，而漏桶主要目的是平滑流入速率。</li>
</ul>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>《亿级流量网站架构核心技术》</li>
<li><a href="https://github.com/vutran1710/PyrateLimiter">https://github.com/vutran1710/PyrateLimiter</a></li>
<li><a href="https://gist.github.com/drocco007/6155452">https://gist.github.com/drocco007/6155452</a></li>
<li><a href="https://vim0.com/post/interview/">https://vim0.com/post/interview/</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>限流</tag>
      </tags>
  </entry>
  <entry>
    <title>学海屠魔录</title>
    <url>/2021/06/20/learning-method/</url>
    <content><![CDATA[<p>英彦有云，魔鬼在细节之中（The devil is in the details），其意义在于提醒人们，不要忽视细节，即使是微不足道的小处也可以影响大局。</p>
<p>换个角度思考，这句话用在学习上，是再贴切不过了。当我们接触一个新领域时，其中的每个知识细节，都是魔鬼，稍有不慎就会被魔鬼击败，产生厌学情绪，再起不能。只有除尽这些魔鬼，才能攀上知识的高峰。</p>
<span id="more"></span>

<p>但光学会这些细节的点还远远不够，你的知识都是僵化的，是零散不成体系的的，难以真正应用。<br>就如将英语单词表背得滚瓜烂熟，倘若不识得语法与文法，断然是写不出文章的。</p>
<p>总地来说，学习分为两个阶段，“为学日益”和“为道日损”，也就是积累具体细节知识点，然后再将这些知识点进行归纳总结整理，将知识抽象并构建体系。</p>
<blockquote>
<p>注：此处的“为学日益”和“为道日损”与道德经中原义不同</p>
</blockquote>
<p>华罗庚教授曾把读书的过程归纳为“由薄到厚”与“由厚到薄”两个阶段，也是异曲同工；硅谷钢铁侠马斯克常说的“第一性原理”，就是第二步“为道日损”所得到的结果；再进一步，禅宗所说的“渐修”和“顿悟”也是一样的。</p>
<p>这两个阶段总体上是同等重要的，但在学习的不同阶段又该有所侧重。</p>
<p>下面将理论展开论述，我们又能引申出一些结论，得到一些新的看法。</p>
<h2 id="为学日益"><a href="#为学日益" class="headerlink" title="为学日益"></a>为学日益</h2><p>先讲第一阶段，这一阶段积累具体细节知识点，那么多大规模的知识点可以成为一个细节呢？</p>
<p>这点因人而异，因当前知识储备而异，评判标准就是能否说出该知识点的定义和用途，如若不能就应该继续往下看构成该知识点的子知识点，直至满足标准为止。</p>
<p>由此可知，学习是有极限的，当最小的知识点都不能领会时，继续学习就无从谈起了。<br>就如数学，当不能理解<code>1 + 1 = 2</code>时，在这之上构建的一切都与你无缘了。</p>
<p>所以学习还是要靠智商的，只不过要求极低。</p>
<p>在这个阶段比较有效的方法有</p>
<ul>
<li>不同体系的知识类比迁移，跟已学会的知识联系，便于记忆</li>
<li>费曼学习法，通过输出的方式，对学会的知识点的掌握程度进行确认。</li>
</ul>
<h2 id="为道日损"><a href="#为道日损" class="headerlink" title="为道日损"></a>为道日损</h2><p>当学习到达一定的阶段，就该对知识进行归纳整理，以便进入下一阶段的学习。</p>
<p>因为人脑所能同时关注的知识点是有上限的，如果不加以归纳整理进行抽象，体现出来就是学了新的忘了旧的。</p>
<p>日常生活的很多场景都与这个有关，比如人们在争辩的时候经常会扣帽子，这就是一种抽象，降低了受众的认知难度，在传播中是有利的。“xx人偷井盖”，这就是典型的扣帽子，一个地方人何其多，每个人又不一样，贴切地描述出这个整体是非常难的，扣帽子就是舍弃了其他特征，用某几个特征来概括整体，这样带了的坏处就是不准确，失之偏颇。</p>
<p>这一阶段影响的是对知识的应用，也就是将一系列知识点抽象成一个整体，用于构造其他知识。</p>
<p>抽象有好的有坏的，知识点的名称就是对其内涵的一种抽象，好的抽象应该是可以望文生义的。就像写代码，好的函数命名应该是表达了函数的用途，也就是对具体实现该函数语句的抽象。</p>
<p>类比也是一种抽象，而且是一种极其高效的抽象，将具体的细节和已有的知识点关联起来。但类比也是危险的，很容易做出不合适的类比，也就是引喻失义。所以类比更像是方便法门，一条通向“第一性原理”的捷径，有其适用场景，不应该作为“第一性原理”本身牢记。</p>
<h2 id="歧路"><a href="#歧路" class="headerlink" title="歧路"></a>歧路</h2><p>由此可以看出很多学习的方法其实都走在歧路上。</p>
<p>如学英语，一味在单词和例句中深挖，最终还是不能流畅的写作交流，这是第二阶段缺失造成的。<br>又如罗辑思维，听他灌输各种大而无当的大道理，只会显得很轻浮，除了增加谈资之外无有用处，这是第一阶段缺失造成的。</p>
<p>最后，希望我这胡言乱语对读者有些微帮助。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>学习方法</tag>
      </tags>
  </entry>
  <entry>
    <title>联想笔记本 BIOS 跳过检测强制降级</title>
    <url>/2025/05/15/levono-bios-force-downgrade/</url>
    <content><![CDATA[<p>联想某些版本的 bios 似乎会禁止降级，即使打开 bios 设置里的允许降级选项，依然会提示 “this platform does not support IHISI interface” 的错误，导致降级失败。</p>
<span id="more"></span>

<h2 id="降级方法"><a href="#降级方法" class="headerlink" title="降级方法"></a>降级方法</h2><p>经过一些尝试，找到了方法绕过，以 <code>ThinkBook 14 G3 ACL</code> 机型为例</p>
<ol>
<li>打开驱动<a href="https://newthink.lenovo.com.cn/driveList.html?selname=21A2004H0D">下载页面</a> </li>
<li>打开 <a href="https://think.lenovo.com.cn/support/driver/driverdetail.aspx?DEditid=134398">BIOS</a> 页面， 下载以下安装文件<ul>
<li>当前版本 BIOS 的安装包（<code>NEW</code>），版本 GQCN41WW_HFCN36WW</li>
<li>以及想要降级的 BIOS (<code>OLD</code>)，版本 GQCN36WW_HFCN31WW</li>
</ul>
</li>
<li>运行 <code>NEW</code>，选择仅解压。找到解压目录，应该只有一个 exe 程序，使用 7-zip 或者 bandizip 打开并提取内部文件。</li>
<li>对 <code>OLD</code> 也执行如上操作，如果没有解压选项，可以尝试直接提取。</li>
<li>将 <code>OLD</code> 提取目录中的 .bin 格式固件（GLV3A036.bin、GLV4D031.bin）复制到 <code>NEW</code> 提取目录中。</li>
<li>修改 <code>NEW</code> 提取目录中的 <code>platform.ini</code> 文件，找到如下内容。将其中的 .bin 文件名，修改为 <code>OLD</code> 中的 .bin 文件名，保存。有两个 .bin 文件，文件名是和版本中的数字对应的，不要修改错误。 <figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">; 修改前</span><br><span class="line">[MULTI_FD]</span><br><span class="line"><span class="attribute">Flag</span>=1</span><br><span class="line">FD#<span class="attribute">01</span>=ID,GLV4D,GLV4D036.bin</span><br><span class="line">FD#<span class="attribute">02</span>=PCI,0,1,1,0,FFFFFFFF,FFFFFFFF,GLV3A041.bin</span><br><span class="line"></span><br><span class="line">; 修改后</span><br><span class="line">[MULTI_FD]</span><br><span class="line"><span class="attribute">Flag</span>=1</span><br><span class="line">FD#<span class="attribute">01</span>=ID,GLV4D,GLV4D031.bin</span><br><span class="line">FD#<span class="attribute">02</span>=PCI,0,1,1,0,FFFFFFFF,FFFFFFFF,GLV3A036.bin</span><br></pre></td></tr></table></figure></li>
<li>运行 <code>NEW</code> 提取目录中的 H2OFFT-Wx64.exe，等待降级完成。</li>
</ol>
<p><strong>注意：有风险，请谨慎操作。不要跨太多版本，可能有不可预料的问题</strong></p>
<p><img data-src="https://image.ponder.work/mweb/2025-05-15---17473119676089.jpg"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://www.geektech.co.nz/lenovo-y700-how-to-enable-bios-downgrade">https://www.geektech.co.nz/lenovo-y700-how-to-enable-bios-downgrade</a></li>
</ul>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>硬件</tag>
      </tags>
  </entry>
  <entry>
    <title>缓存淘汰算法之LFU</title>
    <url>/2021/06/23/lfu-cache/</url>
    <content><![CDATA[<p>Least Frequently Used (LFU) 是一种常见的缓存淘汰算法，译为“最近最不经常使用”，也就是将缓存中使用次数最少的数据淘汰掉。</p>
<p>有两种常见的实现方法</p>
<ul>
<li>小顶堆 + hashmap，插入和删除的复杂度为O(logN), 但淘汰相同访问次数的节点是不稳定的，因为堆排序不稳定。</li>
<li>数组存储数据项 + hashmap记录数据项index, 淘汰缓存的复杂度为O(N)</li>
</ul>
<span id="more"></span>
<p>特点</p>
<ol>
<li>一般情况下，LFU效率要优于LRU，且能够避免周期性或者偶发性的操作导致缓存命中率下降的问题</li>
<li>LFU存在历史数据影响将来数据的”缓存污染”问题。</li>
</ol>
<h2 id="Python-实现"><a href="#Python-实现" class="headerlink" title="Python 实现"></a>Python 实现</h2><p>这里的Python实现是方案1</p>
<p>具体步骤</p>
<ol>
<li>get元素时，如果存在则返回结果并更新访问次数</li>
<li>set元素时，如果存在则更新val并更新访问次数，否则检查是否淘汰缓存并插入新key</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> math <span class="keyword">import</span> log, ceil</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MinHeap</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>._items = [<span class="literal">None</span>]</span><br><span class="line">        <span class="variable language_">self</span>.need_swap = <span class="variable language_">self</span>.more</span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">length</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">len</span>(<span class="variable language_">self</span>._items) - <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">depth</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> ceil(log(<span class="variable language_">self</span>.length+<span class="number">1</span>, <span class="number">2</span>))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">more</span>(<span class="params">self, i, j</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>._items[i] &gt; <span class="variable language_">self</span>._items[j]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">exch</span>(<span class="params">self, i, j</span>):</span><br><span class="line">        <span class="variable language_">self</span>._items[i], <span class="variable language_">self</span>._items[j] = <span class="variable language_">self</span>._items[j], <span class="variable language_">self</span>._items[i]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">swim</span>(<span class="params">self, k</span>):</span><br><span class="line">        <span class="keyword">while</span> k &gt; <span class="number">1</span> <span class="keyword">and</span> <span class="variable language_">self</span>.need_swap(k//<span class="number">2</span>, k):</span><br><span class="line">            <span class="variable language_">self</span>.exch(k//<span class="number">2</span>, k)</span><br><span class="line">            k = k//<span class="number">2</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">sink</span>(<span class="params">self, k</span>):</span><br><span class="line">        <span class="keyword">while</span> <span class="number">2</span> * k &lt;= <span class="variable language_">self</span>.length:</span><br><span class="line">            j = <span class="number">2</span> * k</span><br><span class="line">            <span class="keyword">if</span> j &lt; <span class="variable language_">self</span>.length <span class="keyword">and</span> <span class="variable language_">self</span>.need_swap(j, j+<span class="number">1</span>):</span><br><span class="line">                j += <span class="number">1</span></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> <span class="variable language_">self</span>.need_swap(k, j):</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="variable language_">self</span>.exch(k, j)</span><br><span class="line">            k = j</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">insert</span>(<span class="params">self, val</span>):</span><br><span class="line">        <span class="variable language_">self</span>._items.append(val)</span><br><span class="line">        <span class="variable language_">self</span>.swim(<span class="variable language_">self</span>.length)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">top</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.length &gt; <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="variable language_">self</span>._items[<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">del_top</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.length &gt; <span class="number">0</span>:</span><br><span class="line">            <span class="variable language_">self</span>.exch(<span class="number">1</span>, <span class="variable language_">self</span>.length)</span><br><span class="line">            val = <span class="variable language_">self</span>._items.pop()</span><br><span class="line">            <span class="variable language_">self</span>.sink(<span class="number">1</span>)</span><br><span class="line">            <span class="keyword">return</span> val</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        tmp = []</span><br><span class="line">        seq = <span class="string">&#x27; &#x27;</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="variable language_">self</span>.depth+<span class="number">1</span>):</span><br><span class="line">            l = seq.join([<span class="built_in">str</span>(e) <span class="keyword">for</span> e <span class="keyword">in</span> <span class="variable language_">self</span>._items[<span class="number">2</span>**(i-<span class="number">1</span>):<span class="number">2</span>**i]])</span><br><span class="line">            tmp.append(l)</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;\n&#x27;</span>.join(tmp)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Node</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, key, val=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.key = key</span><br><span class="line">        <span class="variable language_">self</span>.val = val</span><br><span class="line">        <span class="variable language_">self</span>.count = <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__gt__</span>(<span class="params">self, other</span>):</span><br><span class="line">        <span class="keyword">return</span>  <span class="variable language_">self</span>.count &gt; other.count</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="comment"># return &#x27;&lt;Node key=&#123;!r&#125; val=&#123;!r&#125; count=&#123;!r&#125;&gt;&#x27;.format(self.key, self.val, self.count)</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;&#123;&#125;|&#123;!r&#125;&#x27;</span>.<span class="built_in">format</span>(<span class="variable language_">self</span>.key, <span class="variable language_">self</span>.count)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LFUCache</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, size</span>):</span><br><span class="line">        <span class="variable language_">self</span>.cache = &#123;&#125;</span><br><span class="line">        <span class="variable language_">self</span>.heap = MinHeap()</span><br><span class="line">        <span class="variable language_">self</span>.size = size</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">check_expired</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.heap.length == <span class="variable language_">self</span>.size:</span><br><span class="line">            node = <span class="variable language_">self</span>.heap.del_top()</span><br><span class="line">            <span class="variable language_">self</span>.cache.pop(node.key)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">update_count</span>(<span class="params">self, node</span>):</span><br><span class="line">        idx = <span class="variable language_">self</span>.heap._items.index(node)</span><br><span class="line">        node.count += <span class="number">1</span></span><br><span class="line">        <span class="variable language_">self</span>.heap.sink(idx)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get</span>(<span class="params">self, key</span>):</span><br><span class="line">        node = <span class="variable language_">self</span>.cache.get(key, <span class="literal">None</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> node:</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="variable language_">self</span>.update_count(node)</span><br><span class="line">        <span class="keyword">return</span> node.val</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">set</span>(<span class="params">self, key, val</span>):</span><br><span class="line">        node = <span class="variable language_">self</span>.cache.get(key, <span class="literal">None</span>)</span><br><span class="line">        <span class="keyword">if</span> node:</span><br><span class="line">            node.val = val</span><br><span class="line">            <span class="variable language_">self</span>.update_count(node)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        node = Node(key, val)</span><br><span class="line">        <span class="variable language_">self</span>.check_expired()</span><br><span class="line">        <span class="variable language_">self</span>.heap.insert(node)</span><br><span class="line">        <span class="variable language_">self</span>.cache[key] = node</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;&lt;LFU &#123;!r&#125;&gt;&#x27;</span>.<span class="built_in">format</span>(<span class="variable language_">self</span>.cache)</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://en.wikipedia.org/wiki/Least_frequently_used">https://en.wikipedia.org/wiki/Least_frequently_used</a></li>
<li><a href="https://melonshell.github.io/2020/02/07/ds_cache_eli/">https://melonshell.github.io/2020/02/07/ds_cache_eli/</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>缓存</tag>
      </tags>
  </entry>
  <entry>
    <title>使用 iTerm2 管理 Tmux 会话</title>
    <url>/2021/08/02/iterm2-tmux-integration/</url>
    <content><![CDATA[<p>Tmux 是一个终端复用器（terminal multiplexer），非常有用，属于常用的开发工具。</p>
<p>一个典型的例子就是，SSH 登录远程计算机，打开一个远程窗口执行命令。这时，网络突然断线，再次登录的时候，是找不回上一次执行的命令的。因为上一次 SSH 会话已经终止了，里面的进程也随之消失了。</p>
<p>Tmux 可以维持和管理我们的远程终端会话，和服务断线重连后也不会丢失工作状态, 同时可以在一个终端连接中开启多个窗口（window）和窗格（pane）。</p>
<span id="more"></span>
<p>比如，下面就包含了2个窗口和3个窗格<br><img data-src="https://image.ponder.work/mweb/2021-08-03-16279763351692.jpg"></p>
<p>具体 Tmux 的细节和使用可以参考 <a href="https://www.ruanyifeng.com/blog/2019/10/tmux.html">阮一峰的文章</a></p>
<p>但是 Tmux 也存在以下几个问题（个人观点）</p>
<ol>
<li>窗口和会话管理默认是全键盘操作，需要记比较多快捷键</li>
<li>由于窗口是 Tmux 虚拟的, 不支持文本回滚（scrollback），文本复制不完美</li>
<li>在 mouse mode 下，无法复制文本，非 mouse mode，调整窗格比较麻烦</li>
<li>不支持rzsz</li>
</ol>
<p>而 iTerm2 内置了 Tmux 绑定功能，可以将 tmux 的窗口和窗格映射成原生的窗口和窗格，可以用 iTerm2 的菜单和快捷键来操作窗口，解决了前3点问题。</p>
<p>至于第4点，rzsz 由于 tmux 的实现机制决定了是无解的。<br>然而 <a href="https://github.com/lonnywong">lonnywong</a> 实现了替代方案 <a href="https://github.com/trzsz/trzsz">trzsz</a> ，<strong>完美解决了文件上传下载的问题</strong>，亲测非常好用。</p>
<h2 id="iTerm2-配置"><a href="#iTerm2-配置" class="headerlink" title="iTerm2 配置"></a>iTerm2 配置</h2><p>可以对 tmux 窗口的映射进行一些定制</p>
<p>iTerm2 对于 tmux 会话有一个profile，建议对终端颜色和外观进行一些定制化，将原生窗口和 tmux 窗口区分开来。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-08-03-16279767130639.jpg"><br><img data-src="https://image.ponder.work/mweb/2021-08-03-16279767560503.jpg"></p>
<h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>对于终端机器的 Tmux 版本有要求，需要支持<code>-CC</code>命令</p>
<p><strong>具体方法</strong></p>
<ul>
<li>新建tmux会话: <code>tmux -CC</code></li>
<li>断开重连(attach): <code>tmux -CC attach</code></li>
<li>如已存在会话则连接，否则新建会话：<code>tmux -CC new -A -s main</code></li>
<li>如果是ssh连接的机器: <code>ssh -t user@host &#39;tmux -CC new -A -s main&#39;</code></li>
<li>断开连接(dettach): 在连接的窗口按 esc，或者直接关掉连接的 tab 吧</li>
<li>关掉 session(destroy): 关闭当前 session 的所有 tab 即可</li>
</ul>
<p>还可以使用 iTerm 的 tmux dashboard 来管理多个会话。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-08-03-16279791270158.jpg"></p>
<p><img data-src="https://image.ponder.work/mweb/2021-08-03-16279791503013.jpg"></p>
<p><img data-src="https://image.ponder.work/mweb/2021-08-03-16279775548246.jpg"></p>
<h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><ul>
<li>tmux静态编译版本 <a href="https://github.com/mjakob-gh/build-static-tmux/releases">https://github.com/mjakob-gh/build-static-tmux/releases</a></li>
<li>tmux显示颜色 <code>echo &#39;set -g default-terminal &quot;screen-256color&quot;&#39; &gt;&gt; ~/.tmux.conf</code></li>
</ul>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://www.ruanyifeng.com/blog/2019/10/tmux.html">https://www.ruanyifeng.com/blog/2019/10/tmux.html</a></li>
<li><a href="https://www.v2ex.com/t/589453">https://www.v2ex.com/t/589453</a></li>
<li><a href="https://iterm2.com/documentation-tmux-integration.html">https://iterm2.com/documentation-tmux-integration.html</a></li>
<li><a href="https://gitlab.com/gnachman/iterm2/-/wikis/tmux-Integration-Best-Practices">https://gitlab.com/gnachman/iterm2/-/wikis/tmux-Integration-Best-Practices</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>tmux</tag>
      </tags>
  </entry>
  <entry>
    <title>xubuntu自动登录root账户</title>
    <url>/2016/04/16/lightdm%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95root%E8%B4%A6%E6%88%B7/</url>
    <content><![CDATA[<h2 id="设置root密码"><a href="#设置root密码" class="headerlink" title="设置root密码"></a>设置root密码</h2><p>运行 <code>sudo passwd</code><br>根据提示输入root帐户密码。</p>
<h2 id="修改lightdm配置文件"><a href="#修改lightdm配置文件" class="headerlink" title="修改lightdm配置文件"></a>修改lightdm配置文件</h2><p>运行 <code>ls /usr/share/lightdm/lightdm.conf.d/ -al</code><br>-rw-r–r– 1 root root   72 12月  3 03:57 50-greeter-wrapper.conf<br>-rw-r–r– 1 root root   68 12月  3 03:57 50-guest-wrapper.conf<br>-rw-r–r– 1 root root   51 12月  3 03:57 50-xserver-command.conf<br>-rw-r–r– 1 root root  118  4月 16 17:08 <b>60-lightdm-gtk-greeter.conf</b></p>
<p>就是greeter这个文件，不同的发行版可能名字不同。<br>运行 <code>sudo gedit /usr/share/lightdm/lightdm.conf.d/60-lightdm-gtk-greeter.conf</code><br>改完以后是这样</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[SeatDefaults]</span></span><br><span class="line"><span class="attr">autologin-user</span>=root</span><br><span class="line"><span class="attr">greeter-session</span>=lightdm-gtk-greeter</span><br><span class="line"><span class="attr">greeter-show-manual-login</span>=<span class="literal">true</span></span><br><span class="line"><span class="attr">all-guest</span>=<span class="literal">false</span></span><br></pre></td></tr></table></figure>

<h2 id="修改-root-profile"><a href="#修改-root-profile" class="headerlink" title="修改 /root/.profile"></a>修改 <code>/root/.profile</code></h2><p>在刚修改完root权限自动登录后，发现可能开机出现以下提示：</p>
<figure class="highlight subunit"><table><tr><td class="code"><pre><span class="line"><span class="keyword">Error </span>found when loading /root/.profile</span><br><span class="line">stdin:is not a tty</span><br><span class="line">…………</span><br></pre></td></tr></table></figure>
<p>运行 <code>gedit /root/.profile</code><br>打开文件后找到<code>mesg n</code>，将其更改为<code>tty -s &amp;&amp; mesg n</code>。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Linux</tag>
      </tags>
  </entry>
  <entry>
    <title>Lightgbm多线程卡死问题定位</title>
    <url>/2020/01/25/lightgbm-hang-in-multi-thread/</url>
    <content><![CDATA[<h2 id="症状"><a href="#症状" class="headerlink" title="症状"></a>症状</h2><p>最近在开发的lightgbm树模型，发现服务在处理了一定量请求后会卡死，请求无响应。</p>
<p><code>pstack</code>之后发现, 进程卡在libgomp.so这个动态库的函数中. 证实确实是卡死</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">Thread 8 (Thread 0x7f8eb7900700 (LWP 1859)):</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">0  0x00007f8e9def4af1 <span class="keyword">in</span> ?? () from /usr/lib64/libgomp.so.1</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">1  0x00007f8e9def23a0 <span class="keyword">in</span> ?? () from /usr/lib64/libgomp.so.1</span></span><br></pre></td></tr></table></figure>

<h2 id="尝试"><a href="#尝试" class="headerlink" title="尝试"></a>尝试</h2><p>首先尝试google <code>lightgbm hang</code>, 看了前几条记录.</p>
<p>发现,github上的一个<a href="https://github.com/microsoft/LightGBM/issues/2217#issuecomment-501233873">issue</a>, 顺着发现官网文档上早就记录里这个问题, 并且提供了解决办法.</p>
<span id="more"></span>

<p><img data-src="https://image.ponder.work/mweb/2020-01-25-15799334551032.jpg"></p>
<p>意思是说,OpenMP这个库在多线程fork的时候, 存在bug, 对于C&#x2F;C++程序必须在fork完成之后再使用OpenMP的功能.</p>
<p>所以解决方法有两个</p>
<ol>
<li>不使用多线程</li>
<li>使用intel工具链编译lightgbm</li>
</ol>
<h3 id="不使用多线程"><a href="#不使用多线程" class="headerlink" title="不使用多线程"></a>不使用多线程</h3><p>上面说了可以设置<code>nthreads=1</code>, 但是在python库中, 我并没有找到如何设置.</p>
<p>查看文档之后发现, 可以通过pip参数编译安装单线程版本.</p>
<figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line">pip <span class="keyword">install</span> lightgbm --<span class="keyword">install</span>-<span class="keyword">option</span>=--nomp</span><br></pre></td></tr></table></figure>

<p>安装完成后可以将site-packages中库文件复制备份, 方便部署. 参考路径</p>
<figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">~<span class="regexp">/.pyenv/</span>versions<span class="regexp">/3.6.9/</span>lib<span class="regexp">/python3.6/</span>site-packages/lightgbm</span><br></pre></td></tr></table></figure>

<h3 id="intel工具链"><a href="#intel工具链" class="headerlink" title="intel工具链"></a>intel工具链</h3><p>配置intel工具链, 并从零开始编译, 整体流程比较复杂, 也不建议.</p>
<p>这里推荐使用conda来配置环境, 免去很多编译配置烦恼.</p>
<ol>
<li><p>安装<a href="https://docs.conda.io/en/latest/miniconda.html">miniconda</a></p>
</li>
<li><p>conda添加intel channel: <code>conda config --add channels intel</code></p>
</li>
<li><p>安装相关库</p>
 <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">conda create -n idp intelpython3_core python=3</span><br><span class="line"><span class="built_in">source</span> activate idp</span><br><span class="line">conda install lightgbm</span><br></pre></td></tr></table></figure></li>
</ol>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://github.com/microsoft/LightGBM/issues/2217">https://github.com/microsoft/LightGBM/issues/2217</a></li>
<li><a href="https://lightgbm.readthedocs.io/en/latest/FAQ.html#lightgbm-hangs-when-multithreading-openmp-and-using-forking-in-linux-at-the-same-time">https://lightgbm.readthedocs.io/en/latest/FAQ.html#lightgbm-hangs-when-multithreading-openmp-and-using-forking-in-linux-at-the-same-time</a></li>
<li><a href="https://github.com/microsoft/LightGBM/blob/master/python-package/README.rst#build-threadless-version">https://github.com/microsoft/LightGBM/blob/master/python-package/README.rst#build-threadless-version</a></li>
<li><a href="https://software.intel.com/en-us/articles/using-intel-distribution-for-python-with-anaconda">https://software.intel.com/en-us/articles/using-intel-distribution-for-python-with-anaconda</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Lightgbm</tag>
      </tags>
  </entry>
  <entry>
    <title>配置 Linux 作为主力操作系统</title>
    <url>/2025/12/21/linux-as-the-only/</url>
    <content><![CDATA[<p>这些年日常操作系统一直是 windows 和 macOS 交替使用，Linux 一般只作为服务器的操作系统。<br>然而，咖喱味的 Windows 11 （LTSC）用起来实在难受（平时只玩游戏，下载）<br>arm 的 macbook 虽然能效惊人，但是内存金子价格，软件也封闭（点名finder）和傲慢，实在受不了。<br>最后，还是转向 Linux，毕竟现在 wayland 基本堪用，国产软件也随着信创逐渐丰富了。</p>
<span id="more"></span>

<h2 id="发行版选择"><a href="#发行版选择" class="headerlink" title="发行版选择"></a>发行版选择</h2><p>这里选用 KDE Neon user edition，基于 Ubuntu LTS, 有以下优点</p>
<ol>
<li>Ubuntu 用户基数大，有问题容易解决</li>
<li>可以安装星火应用商店，方便使用国产软件</li>
<li>可以用到最新的 KDE 桌面环境，wayland 支持更好</li>
<li>KDE 远程桌面好用（服务端和客户端）</li>
</ol>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">OS:</span> <span class="string">KDE</span> <span class="string">neon</span> <span class="string">User</span> <span class="string">Edition</span> <span class="string">x86_64</span></span><br><span class="line"><span class="attr">Host:</span> <span class="string">MS-7D99</span> <span class="string">(3.0)</span></span><br><span class="line"><span class="attr">Kernel:</span> <span class="string">Linux</span> <span class="number">6.14</span><span class="number">.0</span><span class="number">-33</span><span class="string">-generic</span></span><br><span class="line"><span class="attr">Uptime:</span> <span class="number">1</span> <span class="string">day,</span> <span class="number">19</span> <span class="string">hours,</span> <span class="number">23</span> <span class="string">mins</span></span><br><span class="line"><span class="attr">Packages:</span> <span class="number">2831</span> <span class="string">(dpkg)</span></span><br><span class="line"><span class="attr">Shell:</span> <span class="string">bash</span> <span class="number">5.2</span><span class="number">.21</span></span><br><span class="line"><span class="attr">Display (E2434I-T):</span> <span class="string">1920x1080</span> <span class="string">@</span> <span class="number">60</span> <span class="string">Hz</span> <span class="string">in</span> <span class="number">24</span><span class="string">&quot; [External]</span></span><br><span class="line"><span class="string">Display (KOS2718): 3840x2160 @ 60 Hz (as 1920x1080) in 27&quot;</span> [<span class="string">External</span>] <span class="string">*</span></span><br><span class="line"><span class="attr">DE:</span> <span class="string">KDE</span> <span class="string">Plasma</span> <span class="number">6.4</span><span class="number">.5</span></span><br><span class="line"><span class="attr">WM:</span> <span class="string">KWin</span> <span class="string">(Wayland)</span></span><br><span class="line"><span class="attr">WM Theme:</span> <span class="string">Breeze</span></span><br><span class="line"><span class="attr">Theme:</span> <span class="string">Breeze</span> <span class="string">(Light)</span> [<span class="string">Qt</span>]<span class="string">,</span> <span class="string">Breeze</span> [<span class="string">GTK2/3</span>]</span><br><span class="line"><span class="attr">Icons:</span> <span class="string">Breeze</span> [<span class="string">Qt</span>]<span class="string">,</span> <span class="string">breeze</span> [<span class="string">GTK2/3/4</span>]</span><br><span class="line"><span class="attr">Font:</span> <span class="string">微软雅黑</span> <span class="string">(10pt)</span> [<span class="string">Qt</span>]<span class="string">,</span> <span class="string">微软雅黑</span> <span class="string">(10pt)</span> [<span class="string">GTK2/3/4</span>]</span><br><span class="line"><span class="attr">Terminal:</span> <span class="string">/dev/pts/3</span></span><br><span class="line"><span class="attr">CPU:</span> <span class="string">13th</span> <span class="string">Gen</span> <span class="string">Intel(R)</span> <span class="string">Core(TM)</span> <span class="string">i5-13500</span> <span class="string">(20)</span> <span class="string">@</span> <span class="number">4.80</span> <span class="string">GHz</span></span><br><span class="line"><span class="attr">GPU 1:</span> <span class="string">NVIDIA</span> <span class="string">GeForce</span> <span class="string">RTX</span> <span class="number">4060 </span><span class="string">Ti</span> [<span class="string">Discrete</span>]</span><br><span class="line"><span class="attr">GPU 2:</span> <span class="string">Intel</span> <span class="string">AlderLake-S</span> <span class="string">GT1</span> <span class="string">@</span> <span class="number">1.55</span> <span class="string">GHz</span> [<span class="string">Integrated</span>]</span><br><span class="line"><span class="attr">Memory:</span> <span class="number">25.07</span> <span class="string">GiB</span> <span class="string">/</span> <span class="number">46.67</span> <span class="string">GiB</span> <span class="string">(54%)</span></span><br><span class="line"><span class="attr">Swap:</span> <span class="number">392.00</span> <span class="string">KiB</span> <span class="string">/</span> <span class="number">8.00</span> <span class="string">GiB</span> <span class="string">(0%)</span></span><br></pre></td></tr></table></figure>

<h2 id="硬件要求"><a href="#硬件要求" class="headerlink" title="硬件要求"></a>硬件要求</h2><ol>
<li>NVIDIA 显卡在 Linux 下对 Wayland 支持不好，容易出现卡死或者重启的情况，建议显示器连到核显</li>
<li>有些主板 Linux 兼容性不行，建议使用御三家主板</li>
</ol>
<h2 id="系统设置"><a href="#系统设置" class="headerlink" title="系统设置"></a>系统设置</h2><h3 id="Nvidia-显卡驱动"><a href="#Nvidia-显卡驱动" class="headerlink" title="Nvidia 显卡驱动"></a>Nvidia 显卡驱动</h3><p>显示器插到核显上，只使用 Nvidia 做计算，玩游戏也能调用到显卡。<br>使用开源服务器驱动，解决 Wayland 兼容性问题，防止玩游戏崩溃</p>
<ol>
<li>安装驱动</li>
</ol>
<figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">sudo ubuntu-drivers install <span class="number">580</span>-<span class="keyword">server</span>-<span class="keyword">open</span></span><br></pre></td></tr></table></figure>

<ol start="2">
<li>启用睡眠支持<a href="https://download.nvidia.com/XFree86/Linux-x86_64/570.172.08/README/powermanagement.html">参考</a><br>将显存内容保存，否则唤醒后程序会报错（如CUDA程序）</li>
</ol>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="comment"># /etc/modprobe.d/nvidia-power-management.conf</span></span><br><span class="line">options nvidia <span class="attribute">NVreg_PreserveVideoMemoryAllocations</span>=1 <span class="attribute">NVreg_TemporaryFilePath</span>=/tmp</span><br><span class="line"></span><br><span class="line">sudo update-initramfs</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="docker"><a href="#docker" class="headerlink" title="docker"></a>docker</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> pkg <span class="keyword">in</span> docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; <span class="keyword">do</span> <span class="built_in">sudo</span> apt-get remove <span class="variable">$pkg</span>; <span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># Add Docker&#x27;s official GPG key:</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install ca-certificates curl</span><br><span class="line"><span class="built_in">sudo</span> install -m 0755 -d /etc/apt/keyrings</span><br><span class="line"><span class="built_in">sudo</span> curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> a+r /etc/apt/keyrings/docker.asc</span><br><span class="line"></span><br><span class="line"><span class="comment"># Add the repository to Apt sources:</span></span><br><span class="line"><span class="built_in">echo</span> \</span><br><span class="line">  <span class="string">&quot;deb [arch=<span class="subst">$(dpkg --print-architecture)</span> signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \</span></span><br><span class="line"><span class="string">  <span class="subst">$(. /etc/os-release &amp;&amp; echo <span class="string">&quot;<span class="variable">$&#123;UBUNTU_CODENAME:-<span class="variable">$VERSION_CODENAME</span>&#125;</span>&quot;</span>)</span> stable&quot;</span> | \</span><br><span class="line">  <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/apt/sources.list.d/docker.list &gt; /dev/null</span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> usermod -aG docker <span class="variable">$USER</span></span><br><span class="line">newgrp docker  <span class="comment"># 立即生效（或注销重新登录）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># docker 中调用显卡</span></span><br><span class="line">https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html</span><br></pre></td></tr></table></figure>

<h3 id="ipv6-启用-EUI-64"><a href="#ipv6-启用-EUI-64" class="headerlink" title="ipv6 启用 EUI-64"></a>ipv6 启用 EUI-64</h3><p>可以让 ipv6 的ip后缀根据mac生成，可用于配置防火墙规则<br>使用形如 <code>::&lt;后缀&gt;/::ffff:ffff:ffff:ffff</code> 的掩码</p>
<figure class="highlight dos"><table><tr><td class="code"><pre><span class="line">nmcli <span class="built_in">con</span> show</span><br><span class="line">nmcli <span class="built_in">con</span> modify &lt;NETWORK NAME&gt; ipv6.addr-gen-<span class="built_in">mode</span> eui64</span><br></pre></td></tr></table></figure>

<h3 id="启用休眠文件"><a href="#启用休眠文件" class="headerlink" title="启用休眠文件"></a>启用休眠文件</h3><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">sudo</span> su</span><br><span class="line"><span class="attribute">cd</span> /</span><br><span class="line"><span class="attribute">truncate</span> -s <span class="number">0</span> swapfile</span><br><span class="line"><span class="comment"># chattr +C swapfile  # disable COW on btrfs</span></span><br><span class="line"><span class="attribute">fallocate</span> -l <span class="number">8</span>G swapfile</span><br><span class="line"><span class="attribute">chmod</span> <span class="number">0600</span> swapfile</span><br><span class="line"><span class="attribute">mkswap</span> swapfile</span><br><span class="line"><span class="attribute">swapon</span> swapfile</span><br></pre></td></tr></table></figure>

<h3 id="使用英文用户文件夹"><a href="#使用英文用户文件夹" class="headerlink" title="使用英文用户文件夹"></a>使用英文用户文件夹</h3><p>用户个人文件夹（文档、下载等），默认是跟随系统语言。<br>可是中文在终端下输入不太方便，建议使用英文用户文件夹名称。<br>方法步骤：</p>
<ol>
<li>语言修改成英文，注销重新登录</li>
<li>打开文件管理器，确认修改为英文</li>
<li>语言修改回中文</li>
<li>打开文件管理器，保留原名称</li>
</ol>
<h3 id="无线网卡调优"><a href="#无线网卡调优" class="headerlink" title="无线网卡调优"></a>无线网卡调优</h3><p>WIFI 启用 WOL（网络唤醒）</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看 WOL 支持</span></span><br><span class="line">iw phy0 wowlan show</span><br><span class="line"><span class="comment"># 开启 WOL 支持</span></span><br><span class="line"><span class="built_in">sudo</span> iw phy0 wowlan <span class="built_in">enable</span> magic-packet</span><br></pre></td></tr></table></figure>

<p>关闭节能模式，解决无线网口入站延迟较大</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF &gt; /etc/NetworkManager/conf.d/wifi-powersave-off.conf</span></span><br><span class="line"><span class="string">[connection]</span></span><br><span class="line"><span class="string">wifi.powersave = 2</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者启动时设置</span></span><br><span class="line"><span class="built_in">sudo</span> iw dev wlo1 <span class="built_in">set</span> power_save off</span><br></pre></td></tr></table></figure>

<h3 id="睡眠调整"><a href="#睡眠调整" class="headerlink" title="睡眠调整"></a>睡眠调整</h3><p>睡眠到内存（suspend）耗电量很低，够用了，没必要开启混合休眠</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 只睡眠到内存</span></span><br><span class="line"><span class="comment"># 修改 /etc/systemd/sleep.conf</span></span><br><span class="line"><span class="attribute">AllowSuspend</span>=<span class="literal">yes</span></span><br><span class="line"><span class="attribute">AllowHibernation</span>=<span class="literal">no</span></span><br><span class="line"><span class="attribute">AllowSuspendThenHibernate</span>=<span class="literal">no</span></span><br><span class="line"><span class="attribute">AllowHybridSleep</span>=<span class="literal">no</span></span><br><span class="line"><span class="attribute">SuspendState</span>=mem standby</span><br><span class="line"></span><br><span class="line"><span class="comment"># 禁用休眠（可选）</span></span><br><span class="line">sudo systemctl mask hibernate.target hybrid-sleep.target</span><br><span class="line"></span><br><span class="line"><span class="comment"># nvida</span></span><br></pre></td></tr></table></figure>

<h3 id="intel-核显性能优化"><a href="#intel-核显性能优化" class="headerlink" title="intel 核显性能优化"></a>intel 核显性能优化</h3><p>10代以上 intel 核显</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="comment"># cat /etc/modprobe.d/i915.conf</span></span><br><span class="line">options i915 <span class="attribute">enable_fbc</span>=1 <span class="attribute">enable_guc</span>=2 <span class="attribute">enable_dc</span>=0</span><br><span class="line"></span><br><span class="line">sudo update-initramfs -u</span><br></pre></td></tr></table></figure>

<h3 id="RDP-远程桌面"><a href="#RDP-远程桌面" class="headerlink" title="RDP 远程桌面"></a>RDP 远程桌面</h3><p>服务端: 使用 KDE 默认的远程桌面 （可惜稳定性一般）<br>客户端: <code>apt install krdc</code></p>
<p>其他配置</p>
<figure class="highlight crmsh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 双屏只显示其中一个屏幕</span></span><br><span class="line"><span class="comment"># 修改 ~/.config/systemd/user/plasma-krdp_server.service</span></span><br><span class="line"><span class="attr">ExecStart=</span>/usr/bin/krdpserver --<span class="literal">monitor</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 重启服务</span></span><br><span class="line">systemctl --<span class="keyword">user</span> <span class="title">daemon-reload</span></span><br><span class="line">systemctl --<span class="keyword">user</span> <span class="title">restart</span> app-org.kde.krdpserver.service</span><br></pre></td></tr></table></figure>

<h3 id="使用新内核（可选）"><a href="#使用新内核（可选）" class="headerlink" title="使用新内核（可选）"></a>使用新内核（可选）</h3><p>发行版默认内核版本可能比较老，如有必要可以更新内核<br><strong>注意</strong>：mainline 的内核与 ubuntu-drivers 的 nvidia 显卡驱动不兼容（所以一般不推荐）</p>
<figure class="highlight smali"><table><tr><td class="code"><pre><span class="line">sudo<span class="built_in"> add-apt-repository </span>ppa:cappelikan/ppa</span><br><span class="line">sudo apt update</span><br><span class="line">sudo apt install mainline</span><br></pre></td></tr></table></figure>

<h2 id="软件使用"><a href="#软件使用" class="headerlink" title="软件使用"></a>软件使用</h2><h3 id="常见应用替代方案"><a href="#常见应用替代方案" class="headerlink" title="常见应用替代方案"></a>常见应用替代方案</h3><ul>
<li>qq: 使用官方linux版本的flatpak打包<ul>
<li>flatpak install com.qq.QQ</li>
</ul>
</li>
<li>任务管理器<ul>
<li>flatpak install io.missioncenter.MissionCenter</li>
</ul>
</li>
<li>词典：使用欧陆词典<ul>
<li>flatpak install net.eudic.dict</li>
</ul>
</li>
<li>微信<ul>
<li>使用星火应用商店打包的官方linux版本</li>
</ul>
</li>
<li>下载管理器：<ul>
<li>motrix：普通http下载</li>
<li>qbittorrent： bt下载</li>
</ul>
</li>
<li>虚拟机<ul>
<li>KVM：性能更好</li>
<li>VMware：易用性更好</li>
</ul>
</li>
<li>游戏<ul>
<li>steam：大部分游戏都可以直接运行，性能损耗也不大</li>
</ul>
</li>
<li>视频播放<ul>
<li>haruna：使用 flatpak 版本</li>
</ul>
</li>
</ul>
<h3 id="KVM-虚拟机"><a href="#KVM-虚拟机" class="headerlink" title="KVM 虚拟机"></a>KVM 虚拟机</h3><p>安装</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install qemu-kvm libvirt-daemon-system virt-manager</span><br><span class="line"><span class="built_in">sudo</span> usermod -aG libvirt,kvm <span class="variable">$USER</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> --now libvirtd</span><br></pre></td></tr></table></figure>

<p>硬盘直通(scsi), 相比下面的方法性能更好</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">disk</span> <span class="attr">type</span>=<span class="string">&#x27;block&#x27;</span> <span class="attr">device</span>=<span class="string">&#x27;disk&#x27;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">driver</span> <span class="attr">name</span>=<span class="string">&#x27;qemu&#x27;</span> <span class="attr">type</span>=<span class="string">&#x27;raw&#x27;</span> <span class="attr">cache</span>=<span class="string">&#x27;none&#x27;</span> <span class="attr">io</span>=<span class="string">&#x27;native&#x27;</span>/&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">source</span> <span class="attr">dev</span>=<span class="string">&#x27;/dev/disk/by-id/ata-TOSHIBA&#x27;</span> <span class="attr">index</span>=<span class="string">&#x27;2&#x27;</span>/&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">target</span> <span class="attr">dev</span>=<span class="string">&#x27;sdg&#x27;</span> <span class="attr">bus</span>=<span class="string">&#x27;scsi&#x27;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">disk</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>硬盘直通（virtio），samba分享时可能卡顿</p>
<figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">&lt;disk type<span class="operator">=</span><span class="string">&quot;block&quot;</span> device<span class="operator">=</span><span class="string">&quot;disk&quot;</span>&gt;</span><br><span class="line">  &lt;driver name<span class="operator">=</span><span class="string">&quot;qemu&quot;</span> type<span class="operator">=</span><span class="string">&quot;raw&quot;</span> cache<span class="operator">=</span><span class="string">&quot;none&quot;</span>/&gt;</span><br><span class="line">  &lt;source dev<span class="operator">=</span><span class="string">&quot;/dev/disk/by-id/ata-ST3000DM008&quot;</span>/&gt;</span><br><span class="line">  &lt;target dev<span class="operator">=</span><span class="string">&quot;sdc&quot;</span> bus<span class="operator">=</span><span class="string">&quot;virtio&quot;</span>/&gt;</span><br><span class="line">&lt;/disk&gt;</span><br></pre></td></tr></table></figure>

<p>无线网口不支持桥接，使用路由绕过</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 修改 /etc/sysctl.conf; sudo sysctl -p 生效</span></span><br><span class="line">net.ipv4.ip_forward = 1</span><br><span class="line">net.ipv4.conf.wlo1.proxy_arp=1</span><br><span class="line">net.ipv4.conf.br-routed.proxy_arp=1</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建网桥和路由</span></span><br><span class="line"><span class="comment">#/etc/systemd/system/bridge-routed.service</span></span><br><span class="line">[Unit]</span><br><span class="line">Description=Create routed bridge br-routed</span><br><span class="line">After=network.target</span><br><span class="line">Wants=network.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=oneshot</span><br><span class="line">RemainAfterExit=<span class="built_in">yes</span></span><br><span class="line">ExecStartPre=/usr/bin/ip <span class="built_in">link</span> add br-routed <span class="built_in">type</span> bridge</span><br><span class="line">ExecStart=/usr/bin/ip <span class="built_in">link</span> <span class="built_in">set</span> br-routed up</span><br><span class="line">ExecStartPost=/usr/bin/ip route add 192.168.1.41/32 dev br-routed</span><br><span class="line">ExecStop=/usr/bin/ip <span class="built_in">link</span> delete br-routed</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改 kvm 配置</span></span><br><span class="line">&lt;interface <span class="built_in">type</span>=<span class="string">&quot;bridge&quot;</span>&gt;</span><br><span class="line">  &lt;mac address=<span class="string">&quot;00:00:00:00:00:00&quot;</span>/&gt;</span><br><span class="line">  &lt;<span class="built_in">source</span> bridge=<span class="string">&quot;br-routed&quot;</span>/&gt;</span><br><span class="line">  &lt;target dev=<span class="string">&quot;vnet0&quot;</span>/&gt;</span><br><span class="line">  &lt;model <span class="built_in">type</span>=<span class="string">&quot;virtio&quot;</span>/&gt;</span><br><span class="line">  &lt;<span class="built_in">alias</span> name=<span class="string">&quot;net0&quot;</span>/&gt;</span><br><span class="line">  &lt;address <span class="built_in">type</span>=<span class="string">&quot;pci&quot;</span> domain=<span class="string">&quot;0x0000&quot;</span> bus=<span class="string">&quot;0x0a&quot;</span> slot=<span class="string">&quot;0x00&quot;</span> <span class="keyword">function</span>=<span class="string">&quot;0x0&quot;</span>/&gt;</span><br><span class="line">&lt;/interface&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 虚拟机使用静态ip</span></span><br><span class="line">   IPv4 地址 . . . . . . . . . . . . : 192.168.1.41(首选)</span><br><span class="line">   子网掩码  . . . . . . . . . . . . : 255.255.255.0</span><br><span class="line">   默认网关. . . . . . . . . . . . . : 192.168.1.1</span><br><span class="line">   DNS 服务器  . . . . . . . . . . . : 192.168.1.1</span><br></pre></td></tr></table></figure>

<h3 id="dolphin-右键菜单自定义快捷方式"><a href="#dolphin-右键菜单自定义快捷方式" class="headerlink" title="dolphin 右键菜单自定义快捷方式"></a>dolphin 右键菜单自定义快捷方式</h3><p>支持文件和文件夹</p>
<p>chmod +  ~&#x2F;.local&#x2F;share&#x2F;kio&#x2F;servicemenus&#x2F;mklink.desktop</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[Desktop Entry]</span></span><br><span class="line"><span class="attr">Type</span>=Service</span><br><span class="line"><span class="attr">ServiceTypes</span>=KonqPopupMenu/Plugin</span><br><span class="line"><span class="attr">MimeType</span>=inode/directory<span class="comment">;application/octet-stream;</span></span><br><span class="line"><span class="attr">Actions</span>=RunMyScript</span><br><span class="line"></span><br><span class="line"><span class="section">[Desktop Action RunMyScript]</span></span><br><span class="line"><span class="attr">Name</span>=制作符号链接</span><br><span class="line"><span class="attr">Icon</span>=emblem-symbolic-link</span><br><span class="line"><span class="attr">Exec</span>=/home/rlj/scripts/mklink.sh <span class="string">&quot;%F&quot;</span></span><br></pre></td></tr></table></figure>

<p>mklink.sh</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"></span><br><span class="line">NAME=$(<span class="built_in">basename</span> <span class="string">&quot;<span class="variable">$1</span>&quot;</span>)</span><br><span class="line"><span class="built_in">ln</span> -s <span class="string">&quot;<span class="variable">$NAME</span>&quot;</span> <span class="string">&quot;<span class="variable">$NAME</span>.lnk&quot;</span></span><br></pre></td></tr></table></figure>

<h3 id="haruna-视频播放器"><a href="#haruna-视频播放器" class="headerlink" title="haruna 视频播放器"></a>haruna 视频播放器</h3><p>haruna 是 mpv 播放器的前端，功能比较齐全</p>
<p>存在的问题:</p>
<ul>
<li>视频播放器首次最大化窗口，总是跳到另一个屏幕<ul>
<li>关闭另一个屏幕，关闭应用，再打开，再最大化，再重新打开屏幕</li>
</ul>
</li>
<li>无法重命名文件<ul>
<li>设置 flatpak 目录权限</li>
</ul>
</li>
</ul>
<h3 id="flatpak-使用"><a href="#flatpak-使用" class="headerlink" title="flatpak 使用"></a>flatpak 使用</h3><figure class="highlight jboss-cli"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 换源</span></span><br><span class="line">flatpak remote-modify flathub <span class="params">--url=https</span>:<span class="string">//mirrors.ustc.edu.cn/flathub</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 目录授权，解决应用无法编辑文件</span></span><br><span class="line">flatpak override <span class="params">--user</span> org.kde.haruna <span class="params">--filesystem=/share</span></span><br></pre></td></tr></table></figure>

<h3 id="挂载-SMB-共享支持-windows-符号链接"><a href="#挂载-SMB-共享支持-windows-符号链接" class="headerlink" title="挂载 SMB 共享支持 windows 符号链接"></a>挂载 SMB 共享支持 windows 符号链接</h3><p>支持原生符号链接（目录连接），双向操作都能同步</p>
<p>步骤</p>
<ul>
<li><p>windows 允许符号链接<br>计算机配置 → 管理模板 → 系统 → 文件系统 → 启用 Win32 长路径<br>计算机配置 → windwows设置 → 安全设置 → 本地策略 → 用户权限分配 → 创建符号链接</p>
</li>
<li><p>samba 挂载选项<br>symlink&#x3D;native</p>
</li>
<li><p>链接时使用相对路径（两个系统绝对路径不一样）</p>
</li>
</ul>
<h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><h3 id="故障排查方法"><a href="#故障排查方法" class="headerlink" title="故障排查方法"></a>故障排查方法</h3><ol>
<li>查看系统日志 journalctl -k -b0</li>
<li>查看用户日志，比如应用崩溃 journalctl –user -b0</li>
<li>用 AI 搜索相关问题，如果无法解决再尝试 google 搜索</li>
</ol>
<h3 id="edge-无法启动"><a href="#edge-无法启动" class="headerlink" title="edge 无法启动"></a>edge 无法启动</h3><p>edge 异常退出后，有概率无法启动</p>
<p>报错</p>
<figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">Microsoft</span> Edge 进程似乎正在使用此用户配置。</span><br><span class="line">Microsoft Edge 已锁定此用户配置以防止损坏。如果你确定没有其他进程正在使用此用户配置，可以将其解锁并重新启动 Microsoft Edge。</span><br></pre></td></tr></table></figure>

<p>处理方法，其他 chromium 内核浏览器可能也类似</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">rm</span> ~/.config/microsoft-edge/SingletonLock</span><br></pre></td></tr></table></figure>

<h3 id="steam-玩游戏卡住"><a href="#steam-玩游戏卡住" class="headerlink" title="steam 玩游戏卡住"></a>steam 玩游戏卡住</h3><p>dmesg 信息</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">7</span>月 <span class="number">27</span> <span class="number">10</span>:<span class="number">48</span>:<span class="number">59</span> neon kernel: x86/split lock detection: #AC: CPU <span class="number">0</span>/KVM/<span class="number">88279</span> took a split_lock trap at address: <span class="number">0</span>xfffff80104613be8</span><br><span class="line"><span class="attribute">7</span>月 <span class="number">27</span> <span class="number">10</span>:<span class="number">51</span>:<span class="number">00</span> neon kernel: x86/split lock detection: #AC: CPU <span class="number">2</span>/KVM/<span class="number">88281</span> took a split_lock trap at address: <span class="number">0</span>xfffff8010465701c</span><br><span class="line"><span class="attribute">7</span>月 <span class="number">27</span> <span class="number">10</span>:<span class="number">52</span>:<span class="number">51</span> neon kernel: x86/split lock detection: #AC: CHTTPClientThre/<span class="number">90929</span> took a split_lock trap at address: <span class="number">0</span>xf098fc4f</span><br><span class="line"><span class="attribute">7</span>月 <span class="number">27</span> <span class="number">10</span>:<span class="number">53</span>:<span class="number">07</span> neon kernel: x86/split lock detection: #AC: CPU <span class="number">1</span>/KVM/<span class="number">88280</span> took a split_lock trap at address: <span class="number">0</span>xfffff8010465701c</span><br></pre></td></tr></table></figure>

<p>解决方法</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 编辑 /etc/default/grub</span></span><br><span class="line">GRUB_CMDLINE_LINUX_DEFAULT=<span class="string">&#x27;quiet splash split_lock_detect=off&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> update-grub</span><br></pre></td></tr></table></figure>

<h3 id="迁移系统-修复引导"><a href="#迁移系统-修复引导" class="headerlink" title="迁移系统&amp;修复引导"></a>迁移系统&amp;修复引导</h3><ol>
<li>进入 livecd，使用 Gparted 复制系统到新硬盘</li>
<li>修改新硬盘分区的 &#x2F;etc&#x2F;fstab 的挂载点 uuid</li>
<li>重建 grub 引导</li>
</ol>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mount /dev/sdaX /mnt/</span><br><span class="line">mount /dev/sda0 /mnt/boot/efi</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> &#123;proc,dev/sys&#125;; <span class="keyword">do</span></span><br><span class="line">  mount --<span class="built_in">bind</span> /<span class="variable">$&#123;i&#125;</span> /mnt/<span class="variable">$&#123;i&#125;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">chroot</span> /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi</span><br></pre></td></tr></table></figure>

<h3 id="睡眠唤醒后很卡"><a href="#睡眠唤醒后很卡" class="headerlink" title="睡眠唤醒后很卡"></a>睡眠唤醒后很卡</h3><p>桌面很卡，cpu 频率上不来，只有 800mhz。<br>映泰主板兼容性问题，换主板解决</p>
<h3 id="终端删除文件到回收站"><a href="#终端删除文件到回收站" class="headerlink" title="终端删除文件到回收站"></a>终端删除文件到回收站</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install trash-cli</span><br><span class="line"><span class="built_in">alias</span> <span class="built_in">rm</span>=trash</span><br></pre></td></tr></table></figure>

<h3 id="electorn-应用无法启动"><a href="#electorn-应用无法启动" class="headerlink" title="electorn 应用无法启动"></a>electorn 应用无法启动</h3><p>报错 chrome sandbox 相关</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 编辑 /etc/sysctl.conf 添加</span></span><br><span class="line">kernel.<span class="attribute">apparmor_restrict_unprivileged_userns</span>=0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 生效</span></span><br><span class="line">sudo sysctl -p</span><br></pre></td></tr></table></figure>

<h3 id="登录界面隐藏无关用户"><a href="#登录界面隐藏无关用户" class="headerlink" title="登录界面隐藏无关用户"></a>登录界面隐藏无关用户</h3><p>比如某些用于文件共享的用户</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 修改 /etc/sddm.conf</span></span><br><span class="line"><span class="section">[Autologin]</span></span><br><span class="line"><span class="attr">Session</span>=plasma</span><br><span class="line"></span><br><span class="line"><span class="section">[Users]</span></span><br><span class="line"><span class="attr">HideUsers</span>=data,data-r,data-s</span><br><span class="line"><span class="attr">HideShells</span>=/usr/sbin/nologin,/bin/<span class="literal">false</span></span><br></pre></td></tr></table></figure>

<h3 id="偶发硬件设备工作不正常"><a href="#偶发硬件设备工作不正常" class="headerlink" title="偶发硬件设备工作不正常"></a>偶发硬件设备工作不正常</h3><p>强制重启后或者系统崩溃后，如果设备不正常，可以尝试重启到 windows 再切回 linux</p>
<h3 id="应用在启动器里找不到"><a href="#应用在启动器里找不到" class="headerlink" title="应用在启动器里找不到"></a>应用在启动器里找不到</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">rm</span> -f ~/.config/menus/</span><br><span class="line">kbuildsycoca6 --noincremental</span><br></pre></td></tr></table></figure>

<h3 id="修复睡眠唤醒问题"><a href="#修复睡眠唤醒问题" class="headerlink" title="修复睡眠唤醒问题"></a>修复睡眠唤醒问题</h3><p>睡眠之后可能出现莫名奇妙的唤醒</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看当前唤醒触发器</span></span><br><span class="line"><span class="built_in">cat</span> /proc/acpi/wakeup | grep <span class="built_in">enable</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看触发器的设备路径</span></span><br><span class="line"><span class="keyword">for</span> p <span class="keyword">in</span> /sys/class/wakeup/*/device/power/wakeup; <span class="keyword">do</span></span><br><span class="line">    dev=$(<span class="built_in">dirname</span> $(<span class="built_in">dirname</span> <span class="variable">$p</span>));</span><br><span class="line">    dev_path=$(<span class="built_in">realpath</span> <span class="variable">$dev</span>)</span><br><span class="line">    <span class="built_in">echo</span> <span class="variable">$&#123;dev_path&#125;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 直接修改是 /proc/acpi/wakeup 是不生效的</span></span><br><span class="line"><span class="comment"># 设置具体设备的方式禁用 rtc 和 AWAC, 位置格式 $&#123;dev_path&#125;/power/wakeup</span></span><br><span class="line"><span class="comment"># 可以将禁用命令配置到 rc.local</span></span><br><span class="line"><span class="built_in">echo</span> disabled | <span class="built_in">tee</span> /sys/devices/platform/rtc_cmos/power/wakeup  <span class="comment"># rtc</span></span><br><span class="line"><span class="built_in">echo</span> disabled | <span class="built_in">tee</span> /sys/devices/platform/rtc_cmos/rtc/rtc0/alarmtimer.0.auto/power/wakeup <span class="comment"># rtc</span></span><br><span class="line"><span class="built_in">echo</span> disabled | <span class="built_in">tee</span> /sys/devices/platform/ACPI000E:00/power/wakeup  <span class="comment"># AWAC</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查是否生效</span></span><br><span class="line"><span class="built_in">cat</span> /proc/acpi/wakeup | grep <span class="built_in">enable</span></span><br></pre></td></tr></table></figure>

<h3 id="apt-设置代理"><a href="#apt-设置代理" class="headerlink" title="apt 设置代理"></a>apt 设置代理</h3><p>部分 apt ppa 源需要代理，但是国内的镜像源却不需要。<br>所以需要给每一个源单独设置代理的功能</p>
<p>有两种设置方式</p>
<ul>
<li>白名单模式（只给需要的配置）</li>
<li>黑名单模式（先设置默认代理， 不需要的设置直连）</li>
</ul>
<p>设置语法</p>
<ul>
<li>设置代理服务器：<code>Acquire::http::Proxy &lt;proxy-server&gt;</code></li>
<li>设置某个域名的代理：<code>Acquire::http::proxy::&lt;domain&gt; &lt;proxy-server|DIRECT&gt;</code></li>
</ul>
<p>示例</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line">#  /etc/apt/apt.conf.d/<span class="number">99</span>proxy</span><br><span class="line"></span><br><span class="line"># 给每种协议的源设置代理，一般设置 https::Proxy 就行了</span><br><span class="line">#Acquire::http::Proxy <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span>;</span><br><span class="line">#Acquire::https::Proxy <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span>;</span><br><span class="line">#Acquire::ftp::Proxy <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span>;</span><br><span class="line">#Acquire::socks::Proxy <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span>;</span><br><span class="line"></span><br><span class="line"># 设置具体域名是否走代理</span><br><span class="line">#Acquire::http::proxy::local.mirror.address <span class="string">&quot;DIRECT&quot;</span>;</span><br><span class="line">#Acquire::http::proxy::HOST_NAME_TO_BE_PROXIED <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span></span><br><span class="line"></span><br><span class="line"># 黑名单模式示例</span><br><span class="line">Acquire::https::Proxy <span class="string">&quot;http://localhost:10808&quot;</span>;</span><br><span class="line">Acquire::https::Proxy::<span class="string">&quot;mirrors.aliyun.com&quot;</span> <span class="string">&quot;DIRECT&quot;</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="限制硬盘读写速度"><a href="#限制硬盘读写速度" class="headerlink" title="限制硬盘读写速度"></a>限制硬盘读写速度</h3><p>某些nvme硬盘的发热很严重, 如果全速运行会导致系统不稳定.<br>所以需要对硬盘速度进行限制</p>
<p>使用 systemd 设置限速</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建配置目录</span></span><br><span class="line">sudo mkdir -p mkdir -p /etc/systemd/system/&#123;user,system&#125;.slice.d/</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置用户限制</span></span><br><span class="line">sudo tee /etc/systemd/system/user.slice.d/io-limit.conf  &gt; /dev/null &lt;&lt;EOF</span><br><span class="line">[Slice]</span><br><span class="line">IOReadBandwidthMax=/dev/nvme0n1 800M</span><br><span class="line">IOWriteBandwidthMax=/dev/nvme0n1 300M</span><br><span class="line">IOReadIOPSMax=/dev/nvme0n1 60000</span><br><span class="line">IOWriteIOPSMax=/dev/nvme0n1 30000</span><br><span class="line"></span><br><span class="line">EOF</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">系统也使用同样的限制</span></span><br><span class="line">sudo ln -sf /etc/systemd/system/user.slice.d/io-limit.conf \</span><br><span class="line">    /etc/systemd/system/system.slice.d/</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>速度测试</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">~$ </span><span class="language-bash"><span class="built_in">dd</span> <span class="keyword">if</span>=/dev/zero of=testfile bs=1M count=2048 oflag=direct status=progress \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; <span class="built_in">rm</span>  testfile</span></span><br><span class="line">2099249152 bytes (2.1 GB, 2.0 GiB) copied, 7 s, 300 MB/s</span><br><span class="line">2048+0 records in</span><br><span class="line">2048+0 records out</span><br><span class="line">2147483648 bytes (2.1 GB, 2.0 GiB) copied, 7.16429 s, 300 MB/s</span><br></pre></td></tr></table></figure>

<h3 id="archlinux-命令行不能使用回收站"><a href="#archlinux-命令行不能使用回收站" class="headerlink" title="archlinux 命令行不能使用回收站"></a>archlinux 命令行不能使用回收站</h3><p>安装 cachyos 时，trash-cli 不能使用，其他应用也不能删除文件到回收站</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -S gvfs </span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Linux</tag>
        <tag>KDE</tag>
      </tags>
  </entry>
  <entry>
    <title>缓存淘汰算法之LRU</title>
    <url>/2021/06/20/lru-cache/</url>
    <content><![CDATA[<p>说到缓存，就必须先了解下计算机的<strong>存储器层次结构</strong>。</p>
<p>存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。</p>
<p>计算机系统中的存储设备都被组织成了一个存储器层次结构，从上至下，设备的访问速度越来越慢、容量越来越大，并且越便宜。</p>
<span id="more"></span>

<p><img data-src="https://image.ponder.work/mweb/2021-06-20-16241743798154.jpg" alt="-w727"></p>
<p>高层的空间速度快容量小，为了充分利用，就必须有个数据替换的规则，决定数据的去留。这就是所谓的<strong>缓存淘汰算法</strong>。</p>
<p>常见的方法</p>
<ul>
<li>先进先出算法（FIFO）：最先进入的内容作为替换对象</li>
<li>最近最少使用算法（LFU）：最近最少使用的内容作为替换对象</li>
<li>最久未使用算法（LRU）：最久没有访问的内容作为替换对象</li>
<li>非最近使用算法（NMRU）：在最近没有使用的内容中随机选择一个作为替换对象</li>
</ul>
<h2 id="LRU-原理"><a href="#LRU-原理" class="headerlink" title="LRU 原理"></a>LRU 原理</h2><p>Least Recently used(LRU) 是最常用的缓存淘汰算法，一般译为“最近最少使用”，不太贴切，其实应该是“最不是最近使用”，也就是将最近一次访问时间最远的数据淘汰掉。</p>
<p>LRU正好体现了时间局部性，也就是，如果一个信息项正在被访问，那么在近期它很可能还会被再次访问。</p>
<p>既然是缓存自然需要数据结构记录key和value，可以使用hashmap来存储，查询和设置的复杂度为O(1)。</p>
<p>同时还需要记录数据最近一次访问时间的次序，可以想到用线性结构存储，由于频繁插入删除，可以用链表实现，新数据在头部，老数据在尾部。<br>由于需要频繁删除数据，而单向链表没有记录前驱节点信息，需要遍历链表，复杂度为O(N)，所以使用双向链表。</p>
<p>具体的原则</p>
<ul>
<li>新数据插入到链表头部，并存入hashmap（value为链表节点指针）；</li>
<li>查找hashmap，当key命中，则将数据移到链表头部；</li>
<li>当链表满的时候，将链表尾部的数据丢弃，删除hashmap对应key；</li>
</ul>
<h3 id="LRU的不足"><a href="#LRU的不足" class="headerlink" title="LRU的不足"></a>LRU的不足</h3><p>当存在热点数据时，LRU的效率很好，但偶发性的、周期性的批量操作会导致LRU命中率急剧下降，缓存污染情况比较严重。</p>
<p><strong>缓存污染</strong>，是指系统将不常用的数据从内存移到缓存，造成常用数据的挤出，降低了缓存效率的现象。</p>
<p>常见改进算法有LFU，LRU-K</p>
<h2 id="Python-实现"><a href="#Python-实现" class="headerlink" title="Python 实现"></a>Python 实现</h2><p>首先需要实现双向链表，引入头节点，并将链表首未连在一起，这样插入和删除的时候就不需要额外判断链表的头部和尾部，简化了实现。</p>
<p>hashmap则使用Python的dict</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Node</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, key, val=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.key = key</span><br><span class="line">        <span class="variable language_">self</span>.val = val</span><br><span class="line">        <span class="variable language_">self</span>.<span class="built_in">next</span> = <span class="literal">None</span></span><br><span class="line">        <span class="variable language_">self</span>.prev = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;Node(&#123;!r&#125;, &#123;!r&#125;)&#x27;</span>.<span class="built_in">format</span>(<span class="variable language_">self</span>.key, <span class="variable language_">self</span>.val)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DoublyLink</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.root = Node(<span class="string">&#x27;root&#x27;</span>)</span><br><span class="line">        <span class="variable language_">self</span>.root.prev = <span class="variable language_">self</span>.root</span><br><span class="line">        <span class="variable language_">self</span>.root.<span class="built_in">next</span> = <span class="variable language_">self</span>.root</span><br><span class="line">        <span class="variable language_">self</span>.length = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">tail</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.root.prev</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">append_head</span>(<span class="params">self, node</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.insert_after(<span class="variable language_">self</span>.root, node)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">append_tail</span>(<span class="params">self, node</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.insert_after(<span class="variable language_">self</span>.tail, node)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">insert_after</span>(<span class="params">self, pos: Node, node</span>):</span><br><span class="line">        pos.<span class="built_in">next</span>.prev = node</span><br><span class="line">        node.<span class="built_in">next</span> = pos.<span class="built_in">next</span></span><br><span class="line">        pos.<span class="built_in">next</span> = node</span><br><span class="line">        node.prev = pos</span><br><span class="line">        <span class="variable language_">self</span>.length += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> node</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">find</span>(<span class="params">self, key</span>):</span><br><span class="line">        ptr = <span class="variable language_">self</span>.root</span><br><span class="line">        <span class="keyword">while</span> ptr.<span class="built_in">next</span> != <span class="variable language_">self</span>.root:</span><br><span class="line">            <span class="keyword">if</span> ptr.<span class="built_in">next</span>.key == key:</span><br><span class="line">                <span class="keyword">return</span> ptr.<span class="built_in">next</span></span><br><span class="line">            ptr = ptr.<span class="built_in">next</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">remove</span>(<span class="params">self, node</span>):</span><br><span class="line">        <span class="keyword">if</span> node == <span class="variable language_">self</span>.root:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        node.<span class="built_in">next</span>.prev = node.prev</span><br><span class="line">        node.prev.<span class="built_in">next</span> = node.<span class="built_in">next</span></span><br><span class="line">        <span class="variable language_">self</span>.length -= <span class="number">1</span></span><br><span class="line">        <span class="keyword">del</span> node</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        tmp = [<span class="string">&#x27;Link(len=&#123;&#125;):&#x27;</span>.<span class="built_in">format</span>(<span class="variable language_">self</span>.length)]</span><br><span class="line">        ptr = <span class="variable language_">self</span>.root</span><br><span class="line">        <span class="keyword">while</span> ptr.<span class="built_in">next</span> != <span class="variable language_">self</span>.root:</span><br><span class="line">            tmp.append(<span class="built_in">repr</span>(ptr.<span class="built_in">next</span>))</span><br><span class="line">            ptr = ptr.<span class="built_in">next</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27; -&gt; &#x27;</span>.join(tmp)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LRUCache</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, size</span>):</span><br><span class="line">        <span class="variable language_">self</span>.cache = &#123;&#125;</span><br><span class="line">        <span class="variable language_">self</span>.dl = DoublyLink()</span><br><span class="line">        <span class="variable language_">self</span>.size = size</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">check_expired</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.dl.length == <span class="variable language_">self</span>.size:</span><br><span class="line">            <span class="variable language_">self</span>.cache.pop(<span class="variable language_">self</span>.dl.tail.key)</span><br><span class="line">            <span class="variable language_">self</span>.dl.remove(<span class="variable language_">self</span>.dl.tail)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">move_to_head</span>(<span class="params">self, node</span>):</span><br><span class="line">        <span class="variable language_">self</span>.dl.remove(node)</span><br><span class="line">        <span class="variable language_">self</span>.dl.append_head(node)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get</span>(<span class="params">self, key</span>):</span><br><span class="line">        node = <span class="variable language_">self</span>.cache.get(key, <span class="literal">None</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> node:</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="variable language_">self</span>.move_to_head(node)</span><br><span class="line">        <span class="keyword">return</span> node.val</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">set</span>(<span class="params">self, key, val</span>):</span><br><span class="line">        node = <span class="variable language_">self</span>.cache.get(key, <span class="literal">None</span>)</span><br><span class="line">        <span class="keyword">if</span> node:</span><br><span class="line">            node.val = val</span><br><span class="line">            <span class="variable language_">self</span>.move_to_head(node)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="variable language_">self</span>.check_expired()</span><br><span class="line">        <span class="variable language_">self</span>.cache[key] = <span class="variable language_">self</span>.dl.append_head(Node(key, val))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;&lt;LRU maxsize=&#123;&#125;, len=&#123;&#125;&gt; &#123;!r&#125;&#x27;</span>.<span class="built_in">format</span>(<span class="variable language_">self</span>.size, <span class="variable language_">self</span>.dl.length, <span class="variable language_">self</span>.cache)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    c = LRUCache(<span class="number">2</span>)</span><br><span class="line">    c.<span class="built_in">set</span>(<span class="string">&#x27;a&#x27;</span>, <span class="number">1</span>)</span><br><span class="line">    <span class="built_in">print</span>(c, c.dl)</span><br><span class="line">    c.<span class="built_in">set</span>(<span class="string">&#x27;b&#x27;</span>, <span class="number">2</span>)</span><br><span class="line">    <span class="built_in">print</span>(c, c.dl)</span><br><span class="line">    c.get(<span class="string">&#x27;a&#x27;</span>)</span><br><span class="line">    <span class="built_in">print</span>(c, c.dl)</span><br><span class="line">    c.<span class="built_in">set</span>(<span class="string">&#x27;c&#x27;</span>, <span class="number">3</span>)</span><br><span class="line">    <span class="built_in">print</span>(c, c.dl)</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>深入理解计算机系统</li>
<li><a href="https://en.wikipedia.org/wiki/Cache_replacement_policies">https://en.wikipedia.org/wiki/Cache_replacement_policies</a></li>
<li><a href="https://github.com/python/cpython/blob/7247f6f433846c6e37308a550e8e5eb6be379856/Lib/functools.py#L525">https://github.com/python/cpython/blob/7247f6f433846c6e37308a550e8e5eb6be379856/Lib/functools.py#L525</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/76553221">https://zhuanlan.zhihu.com/p/76553221</a></li>
<li><a href="https://melonshell.github.io/2020/02/07/ds_cache_eli/">https://melonshell.github.io/2020/02/07/ds_cache_eli/</a></li>
<li><a href="https://segmentfault.com/a/1190000018810255">https://segmentfault.com/a/1190000018810255</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>缓存</tag>
      </tags>
  </entry>
  <entry>
    <title>macOS 使用技巧</title>
    <url>/2021/10/10/macos-tips/</url>
    <content><![CDATA[<p>对程序员来说，macOS 就是一个桌面支持比较好的 Linux&#x2F;Unix，给日常开发带来了许多便利</p>
<p>本文记录一些日常使用中的小技巧。</p>
<span id="more"></span>
<h2 id="macOS-mojave-密码位数限制"><a href="#macOS-mojave-密码位数限制" class="headerlink" title="macOS mojave 密码位数限制"></a>macOS mojave 密码位数限制</h2><p>苹果在10.14的系统上增加了密码最小长度的限制<br>可以通过这个命令去除</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pwpolicy -clearaccountpolicies</span><br></pre></td></tr></table></figure>

<p><strong>注意</strong>：去除限制之后，通过time machine备份的系统，还原到有限制的机器上，可能会导致一些bug。如卡死在登陆页面，部分应用的资源库损坏等等。</p>
<h2 id="重置账户、配置"><a href="#重置账户、配置" class="headerlink" title="重置账户、配置"></a>重置账户、配置</h2><p>如果出现上面的问题，可以尝试重置下系统设置（选择任意一种，建议选1或2）</p>
<ol>
<li>新建管理员账号，在新机器上去除密码限制。</li>
<li>进入恢复模式（开机按command+r），删除对应用户账户配置文件（恢复模式下文件挂载路径可能不同）, 重新开机</li>
</ol>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mv</span> ~/Library/Preferences ~/Library/Preferences.bak</span><br><span class="line"><span class="built_in">mv</span> ~/Library/PreferencePanes ~/Library/PreferencePanes.bak</span><br></pre></td></tr></table></figure>

<ol start="3">
<li>清除机器配置状态，重新设置一个新账户（开机按住command+s；）</li>
</ol>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">/sbin/mount -uaw</span><br><span class="line"><span class="built_in">rm</span> var/db/.applesetupdone</span><br><span class="line">reboot</span><br></pre></td></tr></table></figure>

<h2 id="Mac-开启任何来源选项"><a href="#Mac-开启任何来源选项" class="headerlink" title="Mac 开启任何来源选项"></a>Mac 开启任何来源选项</h2><figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">sudo spctl <span class="comment">--master-disable</span></span><br></pre></td></tr></table></figure>


<h2 id="让系统更流畅"><a href="#让系统更流畅" class="headerlink" title="让系统更流畅"></a>让系统更流畅</h2><p>macOS 10.14 以后系统动画越来越复杂，对显卡要求比较高，导致配置比较老的设备运行起来很卡顿。<br>特别是big sur系统，使用intel核显的机器都不建议安装。</p>
<p>通过关闭一些动画和特效可以让系统更流畅些</p>
<p><img data-src="https://image.ponder.work/mweb/2021-10-10-16338756772783.jpg"></p>
<h2 id="键位修改"><a href="#键位修改" class="headerlink" title="键位修改"></a>键位修改</h2><p>使用 <a href="https://karabiner-elements.pqrs.org/">karabiner</a> 可以对系统全局快捷键和应用快捷键进行修改。</p>
<p>通过配置 karabiner 将 command，control，option 按键进行重映射，可以让 macOS 的键位 和 Windows&#x2F;Linux 接近。</p>
<h2 id="使-macOS-的命令风格与Linux相同"><a href="#使-macOS-的命令风格与Linux相同" class="headerlink" title="使 macOS 的命令风格与Linux相同"></a>使 macOS 的命令风格与Linux相同</h2><p>macOS 的命令应该是 Unix 风格的，和 Linux 有些不同，可选参数必须放在前面，会有些不方便。<br>比如</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">mac</span></span><br><span class="line">➜  ~ /bin/ls /tmp -al</span><br><span class="line">ls: -al: No such file or directory</span><br></pre></td></tr></table></figure>

<p>可以安装 gnu 工具，覆盖这些命令</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">brew install coreutils findutils gnu-tar htop</span><br><span class="line"><span class="built_in">export</span>   PATH=<span class="string">&quot;/usr/local/opt/coreutils/libexec/gnubin:<span class="variable">$PATH</span>&quot;</span></span><br><span class="line"><span class="built_in">export</span>  MANPATH=<span class="string">&quot;/usr/local/opt/coreutils/libexec/gnuman:<span class="variable">$MANPATH</span>&quot;</span></span><br></pre></td></tr></table></figure>

<h2 id="清除一些-macOS-系统默认快捷键"><a href="#清除一些-macOS-系统默认快捷键" class="headerlink" title="清除一些 macOS 系统默认快捷键"></a>清除一些 macOS 系统默认快捷键</h2><p>创建文件 ~&#x2F;Library&#x2F;KeyBindings&#x2F;DefaultKeyBinding.dict<br>这里是清除了默认的 <code>Control + Command + 方向键</code>的行为</p>
<p>修改完成后必须重新打开现有app才能生效</p>
<figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="string">&quot;^@\UF701&quot;</span> <span class="operator">=</span> (<span class="string">&quot;noop:&quot;</span>)<span class="comment">;</span></span><br><span class="line"><span class="string">&quot;^@\UF702&quot;</span> <span class="operator">=</span> (<span class="string">&quot;noop:&quot;</span>)<span class="comment">;</span></span><br><span class="line"><span class="string">&quot;^@\UF703&quot;</span> <span class="operator">=</span> (<span class="string">&quot;noop:&quot;</span>)<span class="comment">;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>字符含义</p>
<table>
<thead>
<tr>
<th>Prefix</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>~</code></td>
<td>⌥ Option key</td>
</tr>
<tr>
<td><code>$</code></td>
<td>⇧ Shift key</td>
</tr>
<tr>
<td><code>^</code></td>
<td>^ Control key</td>
</tr>
<tr>
<td><code>@</code></td>
<td>⌘ Command key</td>
</tr>
<tr>
<td><code>#</code></td>
<td>keys on number pad</td>
</tr>
</tbody></table>
<p>参考：</p>
<ul>
<li><a href="http://xahlee.info/kbd/osx_keybinding.html">http://xahlee.info/kbd/osx_keybinding.html</a></li>
<li><a href="https://blog.victormendonca.com/2020/04/27/how-to-change-macos-key-bindings/">https://blog.victormendonca.com/2020/04/27/how-to-change-macos-key-bindings/</a></li>
</ul>
<h2 id="关闭启动声音"><a href="#关闭启动声音" class="headerlink" title="关闭启动声音"></a>关闭启动声音</h2><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 关闭提示音</span></span><br><span class="line">sudo nvram <span class="attribute">StartupMute</span>=%01</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打开duang</span></span><br><span class="line">sudo nvram <span class="attribute">StartupMute</span>=%00</span><br></pre></td></tr></table></figure>

<h2 id="crontab-文件路径"><a href="#crontab-文件路径" class="headerlink" title="crontab 文件路径"></a>crontab 文件路径</h2><p>用户执行<code>crontab -e</code>之后定时任务存储位置<code>/private/var/at/tabs/&lt;username&gt;</code></p>
<h2 id="hostname-更改"><a href="#hostname-更改" class="headerlink" title="hostname 更改"></a>hostname 更改</h2><p>macos 默认没有设置hostname，导致终端<code>PS1</code>显示时会用网卡的mac地址作为主机名，看起来比较别扭。</p>
<p>可以通过<code>sudo scutil --set HostName &lt;name&gt;</code>设置想要的主机名</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">➜  ~ hostname</span><br><span class="line">a8a159010000</span><br><span class="line">➜  ~ scutil --<span class="built_in">get</span> HostName</span><br><span class="line">HostName: <span class="keyword">not</span> <span class="built_in">set</span></span><br><span class="line">➜  ~ sudo scutil --<span class="built_in">set</span> HostName Mac-mini</span><br><span class="line">Password:</span><br><span class="line">➜  ~ hostname</span><br><span class="line">ruandeMac-mini</span><br></pre></td></tr></table></figure>

<h2 id="catalina-禁用系统更新提示"><a href="#catalina-禁用系统更新提示" class="headerlink" title="catalina 禁用系统更新提示"></a>catalina 禁用系统更新提示</h2><p>在终端中输入</p>
<figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">defaults</span> write com.apple.systempreferences AttentionPrefBundleIDs <span class="number">0</span> ; <span class="attribute">killall</span>  Dock</span><br></pre></td></tr></table></figure>

<p>在 &#x2F;etc&#x2F;hosts 中加入</p>
<figure class="highlight accesslog"><table><tr><td class="code"><pre><span class="line"># disable mac update</span><br><span class="line"><span class="number">0.0.0.0</span> swdist.apple.com.edgekey.net</span><br><span class="line"><span class="number">0.0.0.0</span> swdist.apple.com.akadns.net</span><br></pre></td></tr></table></figure>

<h2 id="固定dock位置，防止在不同屏幕间移动"><a href="#固定dock位置，防止在不同屏幕间移动" class="headerlink" title="固定dock位置，防止在不同屏幕间移动"></a>固定dock位置，防止在不同屏幕间移动</h2><p>不是100%有效</p>
<figure class="highlight arduino"><table><tr><td class="code"><pre><span class="line">defaults write com.apple.Dock position-immutable -<span class="type">bool</span> yes; killall Dock</span><br></pre></td></tr></table></figure>

<h2 id="不生成-开头的隐藏文件"><a href="#不生成-开头的隐藏文件" class="headerlink" title="不生成 ._ 开头的隐藏文件"></a>不生成 <code>._</code> 开头的隐藏文件</h2><figure class="highlight arduino"><table><tr><td class="code"><pre><span class="line">defaults write com.apple.desktopservices DSDontWriteNetworkStores -<span class="type">bool</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>mac</tag>
      </tags>
  </entry>
  <entry>
    <title>电影《活着》随感</title>
    <url>/2017/03/12/movie-huozhe/</url>
    <content><![CDATA[<p>　　几年前看过小说，情节都早已淡忘，回想起来只留着忧伤的感觉。偶然看到这电影，虽然情节与原著有些不同，但都让人百感交集。因为其实活着的故事就是父辈人的故事，里面的很多类似的事都听老一辈讲过。</p>
<p>　　鲁迅曾说过，悲剧是把美好的东西撕碎给人看。人生，又何尝不是呢。活着的故事虽然与时代背景有些关系，小人物在其中很是无力，被历史的洪流裹挟着近乎无路可走。但是，看当今的世上，在我们的视野之外，也还有无数的悲剧在发生。当悲剧发生在别人身上时，那是新闻，那是故事；当发生在你身上时，你却无处述说。所以，我很少看新闻，特别是报道苦难的新闻，看多了会觉得悲伤而又无力。</p>
<p>　　其实某个角度来说，社会一直都没有变过，也就是阶级。每个小镇上都龙二，也有富贵；只是后来他们的地位转换了。阶级就如同金字塔一般，每个人就其占有资源的多少处在不同的层级上，下层的人想往上爬，一切都是赤裸裸的丛林法则。每个人都在往上面看，羡慕上方的美好生活，有谁会往下看一眼，下面是一群穷困潦倒的富贵，如同蝼蚁，在苦苦挣扎。</p>
<p>　　只要有社会存在，阶级是必然存在的。假使都不算父辈积累的资源，从零开始，大家都处于通过生产物品进行交换的阶段。由于每个人天生是不同的，体力、智商、寿命等属性的总值是不同的，这样大家的生产率就会不同，几代几十代之后差距越来越大（和复利类似），阶级就不可避免地产生了。改朝换代也好，革命也好，都只是重新洗牌而已。</p>
<p>　　唉，越想越是悲哀。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
  </entry>
  <entry>
    <title>MySQL 自定义数据库路径</title>
    <url>/2021/10/14/mysql-custom-location/</url>
    <content><![CDATA[<p><em>最近的一些文章是整理以前的笔记</em><br>MySQL 是最常用的数据，有时希望将数据库文件存放在自定义路径，或者在系统中启动多个 MySQL服务。</p>
<span id="more"></span>
<p>当然，如果条件允许，建议直接使用 docker </p>
<h2 id="创建-my-cnf-配置文件"><a href="#创建-my-cnf-配置文件" class="headerlink" title="创建 my.cnf 配置文件"></a>创建 my.cnf 配置文件</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">datadir</span>=/home/ruan/data/mysql_data</span><br><span class="line"><span class="attr">socket</span>=/home/ruan/data/mysql.sock</span><br><span class="line"><span class="attr">user</span>=ruan</span><br><span class="line"><span class="comment"># Disabling symbolic-links is recommended to prevent assorted security risks</span></span><br><span class="line"><span class="attr">symbolic-links</span>=<span class="number">0</span></span><br><span class="line"><span class="attr">bind-address</span>=<span class="number">127.0</span>.<span class="number">0.1</span></span><br><span class="line"><span class="attr">port</span> = <span class="number">12345</span></span><br><span class="line"></span><br><span class="line"><span class="attr">character-set-server</span>=utf8</span><br><span class="line"><span class="attr">collation-server</span>=utf8_general_ci</span><br><span class="line"></span><br><span class="line"><span class="section">[mysqld_safe]</span></span><br><span class="line"><span class="attr">log-error</span>=/home/ruan/data/mysqld.log</span><br><span class="line"><span class="attr">pid-file</span>=/home/ruan/data/mysqld.pid</span><br></pre></td></tr></table></figure>

<h2 id="启动和初始化"><a href="#启动和初始化" class="headerlink" title="启动和初始化"></a>启动和初始化</h2><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">启动MySQL</span></span><br><span class="line">mysqld_safe --defaults-file=my.cnf --user=ruan</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">初始化数据库</span></span><br><span class="line">mysql_install_db --defaults-file=my.cnf --user=ruan</span><br></pre></td></tr></table></figure>

<h2 id="目录结构"><a href="#目录结构" class="headerlink" title="目录结构"></a>目录结构</h2><figure class="highlight haskell"><table><tr><td class="code"><pre><span class="line">$ tree <span class="class"><span class="keyword">data</span></span></span><br><span class="line"><span class="class"><span class="keyword">data</span></span></span><br><span class="line">├── my.cnf</span><br><span class="line">├── mysql_data</span><br><span class="line">│   ├── ibdata1</span><br><span class="line">│   ├── ib_logfile0</span><br><span class="line">│   ├── ib_logfile1</span><br><span class="line">├── mysqld.log</span><br><span class="line">├── mysqld.pid</span><br><span class="line">└── mysql.sock</span><br></pre></td></tr></table></figure>

<h2 id="设置密码"><a href="#设置密码" class="headerlink" title="设置密码"></a>设置密码</h2><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">mysql&gt; use mysql; <span class="keyword">update</span> <span class="keyword">user</span> <span class="keyword">set</span> <span class="keyword">password</span>=<span class="keyword">password</span>(<span class="string">&#x27;m654321&#x27;</span>) <span class="keyword">where</span> <span class="keyword">user</span>=<span class="string">&#x27;root&#x27;</span>; flush <span class="keyword">privileges</span>;</span><br></pre></td></tr></table></figure>]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>MySQL</tag>
      </tags>
  </entry>
  <entry>
    <title>MySQL 表分区使用</title>
    <url>/2021/10/10/mysql-partition/</url>
    <content><![CDATA[<p>使用MySQL数据库时，当表的数据条数比较大时（1000w以上），数据查询会很慢，索引的效果也不好。</p>
<p>这时我们可以把表的数据分区存储，安装数据值的前缀或者时间字段来分区。</p>
<span id="more"></span>
<h2 id="建表"><a href="#建表" class="headerlink" title="建表"></a>建表</h2><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> test_part (</span><br><span class="line">    appid <span class="type">int</span>(<span class="number">11</span>),</span><br><span class="line">    val <span class="type">int</span>(<span class="number">11</span>),</span><br><span class="line">    username <span class="type">VARCHAR</span>(<span class="number">25</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line">    start_time DATETIME</span><br><span class="line">)</span><br><span class="line"><span class="keyword">PARTITION BY RANGE</span> (TO_DAYS(start_time) )(</span><br><span class="line">    <span class="keyword">PARTITION</span> p20190305 <span class="keyword">VALUES</span> LESS THAN (TO_DAYS(<span class="string">&#x27;2019-03-06 00:00:00&#x27;</span>) )</span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<h2 id="删除分区"><a href="#删除分区" class="headerlink" title="删除分区"></a>删除分区</h2><p>alter table test_part drop partition p1;</p>
<p>不可以删除hash或者key分区。</p>
<p>一次性删除多个分区，alter table test_part drop partition p1,p2;</p>
<h2 id="增加分区"><a href="#增加分区" class="headerlink" title="增加分区"></a>增加分区</h2><p>ALTER TABLE test_part ADD partition (partition p20190306 VALUES LESS THAN (TO_DAYS(‘2019-03-07 00:00:00’)));  </p>
<h2 id="生成测试数据"><a href="#生成测试数据" class="headerlink" title="生成测试数据"></a>生成测试数据</h2><p>创建储存过程</p>
<figure class="highlight oxygene"><table><tr><td class="code"><pre><span class="line">DROP <span class="keyword">PROCEDURE</span> <span class="title function_">IF</span> <span class="title function_">EXISTS</span> <span class="title function_">proc1</span>;</span><br><span class="line">DELIMITER $$</span><br><span class="line"><span class="keyword">SET</span> AUTOCOMMIT = <span class="number">0</span>$$</span><br><span class="line"><span class="keyword">CREATE</span>  <span class="keyword">PROCEDURE</span> <span class="title function_">proc1</span><span class="params">()</span></span><br><span class="line"><span class="title function_">BEGIN</span></span><br><span class="line"><span class="title function_">DECLARE</span> <span class="title function_">v_cnt</span> <span class="title function_">DECIMAL</span> <span class="params">(10)</span>  <span class="title function_">DEFAULT</span> 0 ;</span><br><span class="line">dd:<span class="keyword">LOOP</span></span><br><span class="line">    INSERT  <span class="keyword">INTO</span> test_part VALUES (</span><br><span class="line">        FLOOR(RAND()*<span class="number">100</span>), </span><br><span class="line">        FLOOR(RAND()*<span class="number">1000</span>), </span><br><span class="line">        UUID(), </span><br><span class="line">        DATE_ADD(<span class="string">&#x27;2019-03-04 00:00:00&#x27;</span>, INTERVAL FLOOR(v_cnt / <span class="number">5000</span>) MINUTE)</span><br><span class="line">    )<span class="punctuation">;</span></span><br><span class="line">    COMMIT<span class="punctuation">;</span></span><br><span class="line">    <span class="keyword">SET</span> v_cnt = v_cnt+<span class="number">1</span> <span class="punctuation">;</span></span><br><span class="line">        <span class="keyword">IF</span>  v_cnt = <span class="number">10000000</span> <span class="keyword">THEN</span> LEAVE dd<span class="punctuation">;</span></span><br><span class="line">        <span class="keyword">END</span> <span class="keyword">IF</span><span class="punctuation">;</span></span><br><span class="line">    <span class="keyword">END</span> <span class="keyword">LOOP</span> dd <span class="punctuation">;</span></span><br><span class="line"><span class="keyword">END</span><span class="punctuation">;</span>$$</span><br><span class="line">DELIMITER <span class="punctuation">;</span></span><br></pre></td></tr></table></figure>

<p>调用储存过程</p>
<figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">call proc1<span class="comment">;</span></span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>MySQL</tag>
      </tags>
  </entry>
  <entry>
    <title>Hyper-V 中安装 openmediavault(OMV) 实现完美 NAS 文件共享</title>
    <url>/2021/09/21/openmediavault-on-hyperv/</url>
    <content><![CDATA[<p>一直在寻找适合自己的 NAS 存储方案，也做过一些尝试，可总有些不完美的点。</p>
<p>最近终于找到一个接近完美的方案：<code>Hyper-V + NFS + openmediavault</code></p>
<span id="more"></span>

<h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><p>我对 NAS 几点硬性要求</p>
<ol>
<li>能支持 NTFS 文件系统，否则现有数据迁移成本很高</li>
<li>文件共享必须支持回收站，否则数据易丢失</li>
<li>文件检索要方便</li>
</ol>
<p>所以之前一直用 windows 作为 NAS 系统，最大的问题是不支持共享文件夹的回收站。</p>
<p>现有 NAS 方案的对比</p>
<table>
<thead>
<tr>
<th></th>
<th>good</th>
<th>bad</th>
</tr>
</thead>
<tbody><tr>
<td>windows smb</td>
<td>性能</td>
<td>无法扩展功能（如smb回收站，timemachine）</td>
</tr>
<tr>
<td>wsl1 samba</td>
<td>性能略低于smb，功能可扩展</td>
<td>部分共享文件无法打开,  配置略繁琐</td>
</tr>
<tr>
<td>wsl2 samba（omv）</td>
<td>功能可扩展</td>
<td>无法解决桥接网络，需要更好的cpu</td>
</tr>
<tr>
<td>nfs</td>
<td>性能最强</td>
<td>无法扩展功能</td>
</tr>
<tr>
<td>群晖 &#x2F; OMV &#x2F; FreeNAS</td>
<td>功能多，功能可扩展</td>
<td>无法管理底层文件</td>
</tr>
<tr>
<td>windows + nfs + vmware(群晖)</td>
<td>可管理底层文件，功能可扩展</td>
<td>物理机休眠虚拟机无法唤醒</td>
</tr>
<tr>
<td>windows + nfs + hyper-v(omv）</td>
<td>功能可扩展，性能接近smb，可休眠</td>
<td>需要更好的cpu，hyperv使用略繁琐</td>
</tr>
</tbody></table>
<p>本方案的思路受之前文章的启发 <a href="/2021/01/02/install-openmediavault-in-WSL2/">在WSL2中安装openmediavault(OMV)</a></p>
<p>WSL2 的便利之处在于让 linux 系统能以一个足够快的速度访问 windows 文件，据了解是使用 9p(Plan 9 9p remote filesystem protocol) 协议进行文件共享。</p>
<p>那么我们只要在 Hyper-V 上运行虚拟机，并通过某种方式（这里选择NFS）共享文件到虚拟机里，就能绕开 WSL2 的缺点。<br>实现 NAS 的底层文件系统由 windows 管理， 上层文件共享等应用由虚拟机中的 NAS 系统处理，兼顾了稳定性和扩展性。</p>
<h2 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h2><p>软件环境选择</p>
<ul>
<li>windows 10 21H1</li>
<li>haneWIN NFS server 1.2.59</li>
<li>openmediavault</li>
</ul>
<h3 id="Windows-10-安装及配置"><a href="#Windows-10-安装及配置" class="headerlink" title="Windows 10 安装及配置"></a>Windows 10 安装及配置</h3><ol>
<li>设置开机自动登录用户（可选）: <a href="https://zhuanlan.zhihu.com/p/61262940">https://zhuanlan.zhihu.com/p/61262940</a></li>
<li>禁止自动从睡眠中唤醒（可选）: 控制面板 -&gt; 电源选项 -&gt; 更改计划设置 -&gt; 更改高级计划设置 -&gt; 睡眠 -&gt; 允许使用睡眠唤醒定时器（禁用）</li>
<li>启用 Hyper-V：<code>DISM /Online /Enable-Feature /All /FeatureName:Microsoft-Hyper-V</code></li>
<li>Hyper-V 配置桥接网络：虚拟交换机管理器 -&gt; 新建虚拟网络交换机(外部)</li>
<li>安装 haneWIN NFS server 并配置需要共享的硬盘</li>
</ol>
<h3 id="openmediavault-安装及配置"><a href="#openmediavault-安装及配置" class="headerlink" title="openmediavault 安装及配置"></a>openmediavault 安装及配置</h3><ol>
<li>创建 Hyper-V 虚拟机：选择第一代，选择桥接网卡，虚拟硬盘需挂载到 IDE 控制器</li>
<li>挂载安装镜像，安装 openmediavault 系统</li>
<li>安装 omv-extras <figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">wget -O - https:<span class="regexp">//gi</span>thub.com<span class="regexp">/OpenMediaVault-Plugin-Developers/</span>packages<span class="regexp">/raw/m</span>aster/install | bash</span><br></pre></td></tr></table></figure></li>
<li>安装插件：openmediavault-sharerootfs(共享系统分区的文件夹) openmediavault-remotemount(远程挂载)</li>
<li>挂载 NFS 共享：存储器 -&gt; 远程挂载</li>
<li>建立共享文件夹: 选择系统分区上的 NFS 挂载文件夹</li>
<li>启用smb共享</li>
<li>关闭虚拟机，并配置虚拟机的网卡为静态MAC地址（如果是导入的虚拟机，注意检查MAC地址是否合法）</li>
</ol>
<p><img data-src="https://image.ponder.work/mweb/2021-09-21-16322275739423.jpg" alt="-w610"><br><img data-src="https://image.ponder.work/mweb/2021-09-21-16322277511161.jpg" alt="-w524"></p>
<h3 id="其他优化配置"><a href="#其他优化配置" class="headerlink" title="其他优化配置"></a>其他优化配置</h3><h4 id="samba-性能优化"><a href="#samba-性能优化" class="headerlink" title="samba 性能优化"></a>samba 性能优化</h4><p>配置页面：服务 -&gt; SMB&#x2F;CIFS -&gt; 设置 -&gt; 高级设置 -&gt; 扩展选项</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">veto files = /.Trashes/<span class="variable">$RECYCLE</span>.BIN<span class="built_in">/System </span>Volume Information/.recycle/</span><br><span class="line">socket options = TCP_NODELAY IPTOS_LOWDELAY SO_KEEPALIVE <span class="attribute">SO_RCVBUF</span>=98304 <span class="attribute">SO_SNDBUF</span>=98304</span><br><span class="line">dead time = 30</span><br><span class="line">getwd cache = <span class="literal">yes</span></span><br><span class="line">min protocol = SMB2</span><br><span class="line"><span class="comment">#max protocol = SMB2</span></span><br><span class="line">aio read size = 40960</span><br><span class="line">aio write size = 40960</span><br><span class="line">write cache size = 262144</span><br><span class="line">large readwrite = <span class="literal">yes</span></span><br><span class="line">fake oplocks = <span class="literal">yes</span></span><br><span class="line">oplocks = <span class="literal">no</span></span><br></pre></td></tr></table></figure>

<h4 id="samba-回收站优化"><a href="#samba-回收站优化" class="headerlink" title="samba 回收站优化"></a>samba 回收站优化</h4><p>默认的回收站不能按天对文件进行分类，不方便进行清理</p>
<p>配置页面：服务 -&gt; SMB&#x2F;CIFS -&gt; 共享 -&gt; 共享文件夹<br>启用回收站选项，并在扩展选项中增加</p>
<figure class="highlight arcade"><table><tr><td class="code"><pre><span class="line">recycle:repository = .recycle/%U/<span class="built_in">today</span></span><br></pre></td></tr></table></figure>

<p>配置页面：系统 -&gt; 计划任务<br>增加共享文件夹回收站整理脚本</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -x -e</span><br><span class="line"></span><br><span class="line">BASE=/srv/nfs_root</span><br><span class="line">TODAY=$(<span class="built_in">date</span> +<span class="string">&#x27;%Y%m%d&#x27;</span>)</span><br><span class="line">USERS=(</span><br><span class="line">    data</span><br><span class="line">)</span><br><span class="line"><span class="comment"># read -r -a USERS &lt;&lt;&lt; $(groupmems -g users -l)</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> <span class="variable">$BASE</span></span><br><span class="line"><span class="keyword">for</span> share <span class="keyword">in</span> $(<span class="built_in">ls</span>); <span class="keyword">do</span></span><br><span class="line">    <span class="keyword">for</span> user <span class="keyword">in</span> <span class="variable">$&#123;USERS[@]&#125;</span>; <span class="keyword">do</span></span><br><span class="line">        rec=<span class="string">&quot;<span class="variable">$BASE</span>/<span class="variable">$&#123;share&#125;</span>/.recycle/<span class="variable">$user</span>&quot;</span></span><br><span class="line">        <span class="keyword">if</span> [[ ! -d <span class="variable">$rec</span> ]]; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">mkdir</span> -p <span class="variable">$rec</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">        <span class="built_in">cd</span> <span class="variable">$rec</span></span><br><span class="line">        <span class="keyword">if</span> [[ ! -d <span class="variable">$TODAY</span> ]]; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">mkdir</span> <span class="variable">$TODAY</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">        <span class="keyword">if</span> [[ -L today || -f today ]]; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">rm</span> -f today</span><br><span class="line">        <span class="keyword">elif</span> [[ -d today ]]; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">mv</span> today <span class="variable">$TODAY</span>/$(<span class="built_in">date</span> +<span class="string">&#x27;bak.%s&#x27;</span>)</span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">        <span class="built_in">ln</span> -s ./<span class="variable">$TODAY</span> today</span><br><span class="line">    <span class="keyword">done</span></span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure>

<p>windows 系统中，增加定时任务，删除NFS共享目录中的回收站文件到系统回收站<br>Python 代码如下，超过一定时间的文件会被送到 windows 回收站</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"><span class="keyword">from</span> datetime <span class="keyword">import</span> datetime, timedelta</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">from</span> send2trash <span class="keyword">import</span> send2trash</span><br><span class="line"></span><br><span class="line">os.chdir(os.path.dirname(__file__))</span><br><span class="line"></span><br><span class="line">logging.basicConfig(</span><br><span class="line">    <span class="built_in">format</span>=<span class="string">&#x27;[%(levelname)s %(asctime)s %(filename)s:%(lineno)d]\t%(message)s&#x27;</span>,</span><br><span class="line">    level=<span class="string">&#x27;INFO&#x27;</span>,</span><br><span class="line">    filename=<span class="string">&#x27;log/recycle.log&#x27;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">EXPORTS_FILE=<span class="string">&#x27;D:\\nas_file\\nfsd\\exports&#x27;</span></span><br><span class="line">KEEP_DAYS = <span class="number">30</span></span><br><span class="line">USERS = [<span class="string">&#x27;data&#x27;</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_size</span>(<span class="params">path: <span class="built_in">str</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">sum</span>(p.stat().st_size <span class="keyword">for</span> p <span class="keyword">in</span> Path(path).rglob(<span class="string">&#x27;*&#x27;</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    exports = []</span><br><span class="line">    <span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(EXPORTS_FILE):</span><br><span class="line">        parts = line.strip().split()</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> parts <span class="keyword">or</span> parts[<span class="number">0</span>].startswith(<span class="string">&#x27;#&#x27;</span>):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        exports.append(parts[<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line">    dt = (datetime.now() - timedelta(days=KEEP_DAYS)).strftime(<span class="string">&#x27;%Y%m%d&#x27;</span>)</span><br><span class="line">    <span class="keyword">for</span> export <span class="keyword">in</span> exports:</span><br><span class="line">        <span class="keyword">for</span> user <span class="keyword">in</span> USERS:</span><br><span class="line">            rec = os.path.join(export, <span class="string">&#x27;.recycle&#x27;</span>, user)</span><br><span class="line">            <span class="keyword">if</span> os.path.exists(rec):</span><br><span class="line">                <span class="keyword">for</span> item <span class="keyword">in</span> os.listdir(rec):</span><br><span class="line">                    path = os.path.join(rec, item)</span><br><span class="line">                    <span class="comment"># print(path, get_size(path))</span></span><br><span class="line">                    <span class="keyword">if</span> os.path.isdir(path) <span class="keyword">and</span> item.startswith(<span class="string">&#x27;2&#x27;</span>) <span class="keyword">and</span> item &lt; dt:</span><br><span class="line">                        size = get_size(path)</span><br><span class="line">                        <span class="keyword">if</span> size &lt;= <span class="number">0</span>:</span><br><span class="line">                            logging.info(<span class="string">&#x27;rm empyt\t%s&#x27;</span>, path)</span><br><span class="line">                            os.rmdir(path)</span><br><span class="line">                        <span class="keyword">else</span>:</span><br><span class="line">                            logging.info(<span class="string">&#x27;trash\t%s&#x27;</span>, path)</span><br><span class="line">                            send2trash(path)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            logging.info(<span class="string">&#x27;check&#x27;</span>)</span><br><span class="line">            main()</span><br><span class="line">            time.sleep(<span class="number">3600</span>)</span><br><span class="line">        <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">            logging.exception(e)</span><br></pre></td></tr></table></figure>

<h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>也可考虑将上文 openmediavalut 替换成黑群晖系统，牺牲定制化能力的同时大大增加易用性。<br>当然，你也可以两者双修</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v">https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v</a></li>
<li><a href="https://nelsonslog.wordpress.com/2019/06/01/wsl-access-to-linux-files-via-plan-9/">https://nelsonslog.wordpress.com/2019/06/01/wsl-access-to-linux-files-via-plan-9/</a></li>
</ul>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>NAS</tag>
        <tag>Hyper-V</tag>
      </tags>
  </entry>
  <entry>
    <title>linux添加回收站功能</title>
    <url>/2016/06/12/linux%E6%B7%BB%E5%8A%A0%E5%9B%9E%E6%94%B6%E7%AB%99%E5%8A%9F%E8%83%BD/</url>
    <content><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在linux服务器上工作，常常和<code>rm</code>打交道，难免手滑删除了重要的东西。而且linux又没有回收站功能，一旦删错东西真是欲哭无泪。别问我为什么知道，说多了都是泪。<br><strong>主要思路是用<code>mv</code>命令代替<code>rm</code>，将要删除的东西移动到回收站目录。</strong></p>
<h2 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h2><h3 id="新建删除到回收站脚本"><a href="#新建删除到回收站脚本" class="headerlink" title="新建删除到回收站脚本"></a>新建删除到回收站脚本</h3><p><code>mkdir ~/bin</code></p>
<p><code>vim ~/bin/trash</code>  # 将下面的内容写入</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mv</span> -v <span class="variable">$@</span> <span class="variable">$&#123;trash_bin&#125;</span></span><br></pre></td></tr></table></figure>
<p><code>chmod +x ~/bin/trash</code>   # 增加执行权限</p>
<h3 id="登录时初始化回收站"><a href="#登录时初始化回收站" class="headerlink" title="登录时初始化回收站"></a>登录时初始化回收站</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 将以下内容追加到&quot;~/.bashrc&quot;中，回收站是以每天的日期新建文件夹</span></span><br><span class="line"></span><br><span class="line">today=$(<span class="built_in">date</span> <span class="string">&quot;+%Y%m%d&quot;</span>)  <span class="comment"># 形如20160606格式的日期</span></span><br><span class="line"></span><br><span class="line">trash_bin=<span class="string">&quot;/tmp/trash/<span class="variable">$&#123;today&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ ! -d <span class="string">&quot;/tmp/trash&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">mkdir</span> <span class="string">&quot;/tmp/trash&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ ! -d <span class="variable">$&#123;trash_bin&#125;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">mkdir</span> <span class="variable">$&#123;trash_bin&#125;</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">export</span> trash_bin   <span class="comment"># 加入环境变量</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">alias</span> <span class="built_in">rm</span>=<span class="string">&quot;~/bin/trash&quot;</span></span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Linux</tag>
      </tags>
  </entry>
  <entry>
    <title>OpenWrt执行自定义CGI脚本</title>
    <url>/2020/11/26/openwrt-run-custom-cgi-script/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>我家里的路由器是矿渣newifi3, 刷了OpenWrt系统，可玩性还是非常强的。<br>而且路由器作为24小时在线的设备，很适合作为网络设备的控制中心，比如使用<a href="https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E5%96%9A%E9%86%92">WOL</a>唤醒其他设备。<br>之前就写过一个Python服务，用来控制其他设备的唤醒和睡眠。但是由于newifi3的rom空间十分有限，usb又十分不稳定，Python环境在路由器上还是太重了。<br>所以就想到了利用路由器默认的uhttpd网页服务器，自己编写CGI脚本来实现相应功能。</p>
<span id="more"></span>

<p><em>本方法做参考即可，现在实际使用可以改用本人的 openwrt 应用 <a href="https://github.com/ruanimal/luci-app-quick-action">luci-app-quick-action</a></em></p>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p>以下操作均需要ssh登陆到路由器后台</p>
<h3 id="uhttpd添加lua执行功能"><a href="#uhttpd添加lua执行功能" class="headerlink" title="uhttpd添加lua执行功能"></a>uhttpd添加lua执行功能</h3><p>编辑<code>/etc/config/uhttpd</code>文件</p>
<p>在<code>config uhttpd main</code>部分添加<code>list interpreter &quot;.lua=/usr/bin/lua&quot;</code>， 使uhttpd能够执行lua文件</p>
<p>重启uhttp服务：<code>/etc/init.d/uhttpd restart</code></p>
<p>修改后的配置大致如下</p>
<figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Server configuration</span></span><br><span class="line">config uhttpd main</span><br><span class="line"></span><br><span class="line">	<span class="comment"># HTTP listen addresses, multiple allowed</span></span><br><span class="line">	<span class="keyword">list</span> listen_http	<span class="number">0.0</span>.<span class="number">0.0</span>:<span class="number">80</span></span><br><span class="line">	<span class="keyword">list</span> listen_http	[::]:<span class="number">80</span></span><br><span class="line"></span><br><span class="line">	<span class="comment"># HTTPS listen addresses, multiple allowed</span></span><br><span class="line">	<span class="keyword">list</span> listen_https	<span class="number">0.0</span>.<span class="number">0.0</span>:<span class="number">443</span></span><br><span class="line">	<span class="keyword">list</span> listen_https	[::]:<span class="number">443</span></span><br><span class="line"></span><br><span class="line">	<span class="comment"># Redirect HTTP requests to HTTPS if possible</span></span><br><span class="line">	<span class="keyword">option</span> redirect_https	<span class="number">1</span></span><br><span class="line"></span><br><span class="line">	<span class="comment"># Server document root</span></span><br><span class="line">	<span class="keyword">option</span> home		/www</span><br><span class="line">	</span><br><span class="line">	<span class="comment"># 此处省略其他配置</span></span><br><span class="line">	</span><br><span class="line">	<span class="comment"># List of extension-&gt;interpreter mappings.</span></span><br><span class="line">	<span class="comment"># Files with an associated interpreter can</span></span><br><span class="line">	<span class="comment"># be called outside of the CGI prefix and do</span></span><br><span class="line">	<span class="comment"># not need to be executable.</span></span><br><span class="line"><span class="comment">#	list interpreter	&quot;.php=/usr/bin/php-cgi&quot;</span></span><br><span class="line"><span class="comment">#	list interpreter	&quot;.cgi=/usr/bin/perl&quot;</span></span><br><span class="line">	<span class="keyword">list</span> interpreter <span class="string">&quot;.lua=/usr/bin/lua&quot;</span>  <span class="comment"># 我们添加的内容</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 此处省略其他配置</span></span><br></pre></td></tr></table></figure>

<h3 id="添加CGI脚本"><a href="#添加CGI脚本" class="headerlink" title="添加CGI脚本"></a>添加CGI脚本</h3><p>从上面的配置可以看到，web的默认路径在<code>/www</code><br>我们就在这里新建一个文件夹<code>ctl</code>，来存放我们的CGI脚本, 用于实现NAS的睡眠和唤醒。</p>
<p>目录信息如下</p>
<figure class="highlight vim"><table><tr><td class="code"><pre><span class="line">/www/</span><br><span class="line">├── cgi-bin</span><br><span class="line">│   └── luci</span><br><span class="line">├── ctl</span><br><span class="line">│   ├── <span class="keyword">sleep</span>.<span class="keyword">lua</span></span><br><span class="line">│   └── wakeup.<span class="keyword">lua</span></span><br><span class="line">├── <span class="built_in">index</span>.html</span><br></pre></td></tr></table></figure>

<p>下面开始编写CGI脚本，需要复习以下CGI协议，可以参考<a href="http://ponder.work/2019/09/28/python-web-development/#CGI">本人的文章</a><br>再花几分钟熟悉一下<a href="https://www.runoob.com/lua/lua-tutorial.html">lua语法</a></p>
<h4 id="NAS睡眠脚本"><a href="#NAS睡眠脚本" class="headerlink" title="NAS睡眠脚本"></a>NAS睡眠脚本</h4><p>睡眠的实现是在NAS上写了个简单的Web服务，基于<code>systemctl suspend</code>来实现睡眠<br>然后访问Web服务，触发NAS的睡眠。这里为了和下面的唤醒统一处理，所以通过路由器的CGI转发了一道，直接访问也是可以的。</p>
<figure class="highlight lua"><table><tr><td class="code"><pre><span class="line">#!/usr/bin/lua</span><br><span class="line"><span class="comment">-- /www/ctl/sleep.lua</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">io</span>.<span class="built_in">stdout</span>:<span class="built_in">write</span>(<span class="string">&quot;Content-Type: text/plain\r\n\r\n&quot;</span>)</span><br><span class="line"><span class="keyword">local</span> <span class="built_in">status</span> = <span class="built_in">os</span>.<span class="built_in">execute</span>(<span class="string">&quot;wget -q -O - --timeout 1 -t 1  http://192.168.123.100:8888/ctl/sleep &gt; /dev/null&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">status</span> == <span class="number">0</span> <span class="keyword">then</span></span><br><span class="line">	<span class="built_in">io</span>.<span class="built_in">stdout</span>:<span class="built_in">write</span>(<span class="string">&quot;sleep succ\n&quot;</span>)</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">	<span class="built_in">io</span>.<span class="built_in">stdout</span>:<span class="built_in">write</span>(<span class="string">&quot;sleep fail, already sleep?\n&quot;</span>)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>

<p>使用：<code>curl http://192.168.123.1/ctl/sleep.lua</code></p>
<h4 id="NAS唤醒脚本"><a href="#NAS唤醒脚本" class="headerlink" title="NAS唤醒脚本"></a>NAS唤醒脚本</h4><p>唤醒是基于WOL，原理是在网络上广播特定格式的网络包，目标机器的网卡接受到以后唤醒机器。</p>
<figure class="highlight lua"><table><tr><td class="code"><pre><span class="line">#!/usr/bin/lua</span><br><span class="line"></span><br><span class="line"><span class="built_in">io</span>.<span class="built_in">stdout</span>:<span class="built_in">write</span>(<span class="string">&quot;Content-Type: text/plain\r\n\r\n&quot;</span>)</span><br><span class="line"><span class="comment">-- /usr/bin/etherwake -i &lt;网口&gt; &lt;MAC&gt;</span></span><br><span class="line"><span class="comment">-- 网口需要是和目标机器同网段的接口，在OpenWrt中通常是br-lan</span></span><br><span class="line"><span class="keyword">local</span> <span class="built_in">status</span> = <span class="built_in">os</span>.<span class="built_in">execute</span>(<span class="string">&quot;/usr/bin/etherwake -i br-lan 00:12:34:56:78:90&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">status</span> == <span class="number">0</span> <span class="keyword">then</span></span><br><span class="line">	<span class="built_in">io</span>.<span class="built_in">stdout</span>:<span class="built_in">write</span>(<span class="string">&quot;wakeup succ\n&quot;</span>)</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">	<span class="built_in">io</span>.<span class="built_in">stdout</span>:<span class="built_in">write</span>(<span class="string">&quot;wakeup fail\n&quot;</span>)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>使用：<code>curl http://192.168.123.1/ctl/wakeup.lua</code></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E5%96%9A%E9%86%92">https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E5%96%9A%E9%86%92</a></li>
<li><a href="https://www.zhihu.com/question/31579325/answer/283425839">https://www.zhihu.com/question/31579325/answer/283425839</a></li>
<li><a href="https://www.runoob.com/lua/lua-tutorial.html">https://www.runoob.com/lua/lua-tutorial.html</a></li>
<li><a href="https://openwrt.org/docs/guide-user/services/webserver/uhttpd">https://openwrt.org/docs/guide-user/services/webserver/uhttpd</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>CGI</tag>
        <tag>OpenWrt</tag>
      </tags>
  </entry>
  <entry>
    <title>优雅地使用 Pizza Boy GBA 金手指</title>
    <url>/2021/02/11/pizza-boy-gba-cheat/</url>
    <content><![CDATA[<p>最近偶然又玩起了GBA游戏，瞬间又像是回到了儿时的时光。也碰到了许久以来的老问题，如何作弊，毕竟游戏里刷资源和练级实在是体力劳动，年龄大了毕竟肝不动了。</p>
<p>先说一下，我现在玩GBA的平台是安卓的<code>Pizza Boy GBA</code>模拟器加游戏手柄，总体游戏体验感觉比GBA真机还好，毕竟现在的设备机能放在这，而且还有倍速播放相关功能。</p>
<span id="more"></span>
<h2 id="寻找金手指"><a href="#寻找金手指" class="headerlink" title="寻找金手指"></a>寻找金手指</h2><p>我这次玩的是<code>口袋妖怪叶绿386</code>改版，在网上找的金手指虽然说是386版本金手指，但是经过实际测试都是叶绿原版并不支持386，比如<a href="https://zhidao.baidu.com/question/445579878.html">这个</a>。</p>
<p>金手指的原理其实和通用的单机游戏作弊工具（cheat engine, 金山游侠等）相同，都是修改游戏内存数据。其关键就是找到我们想改的数据的内存地址，所以常规方法都是通过工具搜索相关数值，比如金钱数量，一般要经过多次搜索，可以参考这个<a href="https://jingyan.baidu.com/article/67508eb45a30699cca1ce43d.html">教程</a>。</p>
<p>但GBA好像有些不同，我也曾用cheat engine搜索过，并不能找到相关的内存地址，可能是模拟器的机制导致数值并不能被直接搜索。而且安卓手机上由于权限的问题，类似cheat engine这种读内存的机制肯定是需要root的，这条路就行不通了</p>
<p>最后还是只能走金手指的路子了，毕竟模拟器原生支持，所以又回到了最初的问题，怎么找到正确的金手指。</p>
<h3 id="VBA查找金手指功能"><a href="#VBA查找金手指功能" class="headerlink" title="VBA查找金手指功能"></a>VBA查找金手指功能</h3><p>幸好，Windows平台常用的GBA模拟器<a href="https://dl.3dmgame.com/patch/43400.html">Visual Boy Advance（VBA）</a>带有金手指查找功能，所以先用VBA找到正确的金手指，然后再转换成<code>Piaaz Boy GBA</code>支持的金手指格式。</p>
<p>网上能找到的金手指一般是类似<code>03005102:63</code>格式的，冒号前后表示的是16进制表示的地址和对应的值。而网上找的金手指不对，肯定是因为前面的地址不对。</p>
<p>用VBA加载口袋妖怪叶绿386，先过一段剧情，来到第一个城市（常磐市）的商店，查看商品列表，并选择一个商品。<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130438912515.jpg" alt="-w719"></p>
<p>打开菜单：金手指 -&gt; 查找金手指，点击开始，然后输入<code>21</code>，点击查找，可以看到有几十个变量。<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130439513943.jpg" alt="-w415"></p>
<p>增加想购买的数量到<code>23</code>，然后再次输入23，点击查找，可以看到就剩余一个地址了，显然这个就是我们想要的，如果还有多个地址，就重复这两个流程，再次修改数量再查找。<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130440969581.jpg" alt="-w1145"></p>
<p>选择这条地址记录，添加金手指，把数字设置为98，就会自动添加到金手指列表，并启用该金手指。<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130443147566.jpg" alt="-w417"></p>
<p>金手指列表，我们添加的处于启用状态<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130443911952.jpg" alt="-w417"></p>
<p>在游戏中确认购买，可以看到购买的数量变成98了，但是总价没有变，金手指生效了。<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130443619423.jpg" alt="-w720"></p>
<h3 id="VBA内存查看器功能"><a href="#VBA内存查看器功能" class="headerlink" title="VBA内存查看器功能"></a>VBA内存查看器功能</h3><p>但是这金手指只能修改购买数量，不在商店列表里的商品并不能购买，所以我们必须找到当前选中商品对应的地址，再添加一条相关的金手指。</p>
<p>网上查到的金手指虽然地址不对，但是对应的值（道具的id）应该是对的，下面是口袋妖怪的一些道具id。</p>
<figure class="highlight basic"><table><tr><td class="code"><pre><span class="line"><span class="symbol">0001 </span>大师球</span><br><span class="line"><span class="symbol">0002 </span>超力怪兽球</span><br><span class="line"><span class="symbol">0003 </span>超级球（比怪兽球更厉害些）</span><br><span class="line"><span class="symbol">0004 </span>怪兽球（普通的球）</span><br><span class="line"><span class="symbol">0005 </span>砂狐球（砂狐乐园专用球）</span><br><span class="line"><span class="symbol">0006 </span>触网球（容易抓水和虫类的怪兽）</span><br><span class="line"><span class="symbol">0007 </span>大布斯球（容易抓海底的怪兽）</span><br><span class="line"><span class="symbol">0008 </span>尼斯道球（怪兽越弱越容易抓）</span><br><span class="line"><span class="symbol">0009 </span>利比道球（容易抓抓过的球）</span><br><span class="line"><span class="number">000</span>a 达伊玛球（回合数越长越容易抓）</span><br><span class="line"><span class="number">000</span>b 高基石球（抓到的怪兽变亲密）</span><br><span class="line"><span class="number">000</span>c 布雷密球（珍惜怪兽球）</span><br><span class="line"><span class="number">000</span>d 伤药（体力恢复<span class="number">20</span>）</span><br><span class="line"><span class="number">000</span>e 解毒药（恢复毒状态）</span><br></pre></td></tr></table></figure>

<p>由于已经找到了购买数量的地址，根据编程的常识，选中商品的地址应该也在这个地址附近。<br>所以打开<code>工具 -&gt; 内存查看器</code>定位到地址<code>030050C2</code><br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130463098787.jpg" alt="-w567"></p>
<p>在游戏中，分别选中“解毒药”和“伤药”，并观察内存变化（每次改动完点刷新）。<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130465441636.jpg" alt="-w573"><br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130465892741.jpg" alt="-w569"></p>
<p>由上两张图可以看到，有个地址的值由<code>000E</code>变为<code>000D</code>，正好等于这两个道具的id。<br>所以该地址<code>030050CA</code>就是我们想要的商品地址，我们就增加一条金手指<code>030050CA:0001</code>将购买的商品改为大师球。<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130466584391.jpg" alt="-w573"><br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130467943629.jpg" alt="-w419"></p>
<p>启用这两条金手指，在游戏中点击购买，可以看到生效了。<br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130468908224.jpg" alt="-w477"></p>
<h2 id="在Pizza-Boy-GBA上使用金手指"><a href="#在Pizza-Boy-GBA上使用金手指" class="headerlink" title="在Pizza Boy GBA上使用金手指"></a>在Pizza Boy GBA上使用金手指</h2><p>Pizza Boy GBA并不支持VBA格式的金手指，所以必须进行格式转换，由于VBA格式金手指和CodeBreaker格式金手指转换起来比较接近，只要将地址首位的<code>0</code>替换为<code>8</code>就行了。</p>
<p>转换前</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">030050C2</span>:<span class="number">0062</span></span><br><span class="line"><span class="attribute">030050CA</span>:<span class="number">0001</span></span><br></pre></td></tr></table></figure>

<p>转换后</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">830050C2</span> <span class="number">0062</span></span><br><span class="line"><span class="attribute">830050CA</span> <span class="number">0001</span></span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/2021-02-11-16130489630989.jpg"><br><img data-src="https://image.ponder.work/mweb/2021-02-11-16130489739025.jpg"></p>
<h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>以上查找方法理论上对所有GBA游戏都适用，祝大家玩得愉快！！！</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://zhidao.baidu.com/question/445579878.html">https://zhidao.baidu.com/question/445579878.html</a></li>
<li><a href="https://zhidao.baidu.com/question/1690370269971489228.html">https://zhidao.baidu.com/question/1690370269971489228.html</a></li>
</ol>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>游戏</tag>
      </tags>
  </entry>
  <entry>
    <title>滴答清单与小米手环6 —— 近乎完美的番茄工作法方案</title>
    <url>/2021/04/16/pomodoro-technique-and-mi-band-6/</url>
    <content><![CDATA[<style type="text/css" rel="stylesheet">
img[alt="-w350"] {
	width: 350px
}
</style>

<p>一直在寻找比较好的番茄工作法工具，但是都不那么满意。</p>
<p>物理番茄钟，主要问题是不够灵活，比如调整番茄时长，而且不能和GTD清单同步。</p>
<p>手机app的话，番茄钟结束的提醒声音过于吵闹，特别在公共场合，比如公司或者图书馆；如果静音或者震动的话，又常常感知不到，导致经常关注番茄钟时间，不能集中精力。</p>
<p>也用过安卓智能手表，但目前番茄工作法相关应用还是很少，没有找到合适的，而且手表续航太短，高强度使用基本得每天充电，心智负担大。</p>
<p>之前也用过手机番茄app配合手环，通过手环震动提醒番茄钟结束，方法可行，但是通知经常触达不到，体验不好。</p>
<p>最近<strong>小米手环6</strong>上市了，经过几天摸索，发现配合<strong>滴答清单</strong>的专注功能，以及<strong>小米穿戴</strong>的通知提醒，总体体验挺好，通知也准确，应当是目前最好的番茄工作法方案了。</p>
<span id="more"></span>

<p>下面讲一下具体如何配置</p>
<h2 id="使用步骤"><a href="#使用步骤" class="headerlink" title="使用步骤"></a>使用步骤</h2><ol>
<li><p>小米穿戴app - 我的 - 开启通知相关权限<br><img data-src="https://image.ponder.work/mweb/2021-04-17-v2-083dc66a6eac131538658e0718197f8a_1440w.jpg" alt="-w350"></p>
</li>
<li><p>小米穿戴app - 我的 - 消息通知<br><img data-src="https://image.ponder.work/mweb/2021-04-17-v2-1456fe9137371ebec9f71507c539893d_1440w.jpg" alt="-w350"></p>
</li>
<li><p>小米穿戴app - 我的 - 设备更多设置 - 震动模式 - App通知提醒 - 添加震动模式（可选）<br><img data-src="https://image.ponder.work/mweb/2021-04-17-v2-ff6418a487908a51642bec98c7a17179_1440w.jpg" alt="-w350"></p>
</li>
<li><p>滴答清单app - 番茄专注 - 开始<br><img data-src="https://image.ponder.work/mweb/2021-04-17-v2-32488091b7f87efd080108626d1f9d1f_1440w.jpg" alt="-w350"></p>
</li>
</ol>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>GTD</tag>
        <tag>番茄工作法</tag>
      </tags>
  </entry>
  <entry>
    <title>阻止 bilibili 网页自动关注</title>
    <url>/2026/01/02/prevent-bilibili-auto-follow/</url>
    <content><![CDATA[<p>B 站非常逆天，只要你使用网页端看视频，点开首页推荐的视频，观看一定时长，就有概率会触发自动关注该 UP 主。<br>之前也有怀疑是某个浏览器插件的锅，但更换浏览器依然能够触发这个问题。</p>
<blockquote>
<p>后来发现疑似是 bilibili 默认绑定了按键 <code>G</code> 关注 UP，非常容易误触发。 </p>
</blockquote>
<p>网上发现不少类似的问题，但都没有解决方案，迫于无奈只能使用自定义广告过滤规则了，之后没有再出现问题。</p>
<p>这里使用 uBlock origin, 其他去广告插件也类似, 进入插件设置 -&gt; 自定义静态规则，添加如下规则。<br>在视频页面禁止关注请求，不影响点开 UP 主主页进行关注。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">www.bilibili.com##+js(no-fetch-if, /api\.bilibili\.com\/x\/relation\/modify/, location.pathname.startsWith(<span class="string">&#x27;/video/&#x27;</span>))</span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>Web</tag>
        <tag>uBlock</tag>
      </tags>
  </entry>
  <entry>
    <title>在 Linux 虚拟机中使用 PyAutoGUI 做自动化</title>
    <url>/2025/05/27/pyautogui-in-linux-virtual-machine/</url>
    <content><![CDATA[<p>PyAutoGUI 是 GUI 功能强大自动化方案，但 UI 程序的运行环境选择与配置也是一大难题。</p>
<span id="more"></span>

<h2 id="系统选择"><a href="#系统选择" class="headerlink" title="系统选择"></a>系统选择</h2><p>为了让环境可迁移，免维护，资源消耗少，必须使用虚拟化的方案。<br>虽然 PyAutoGUI 支持 Windows&#x2F;macOS&#x2F;Linux 三个平台。但是各有各的弊端。</p>
<ul>
<li>Windows 的系统臃肿且开发环境不友好</li>
<li>macOS 的权限管理过于严格且虚拟化难度大，高dpi的屏幕也不适合自动化</li>
<li>Linux 就是最好的选择了，但执行的应用可能不支持 Linux，必须引入 Wine</li>
</ul>
<p>综合以上情况考虑，系统环境选择如下</p>
<ul>
<li>pve: 宿主机系统，也可以选择别的环境</li>
<li>debian: 客户机系统，目前是 debian 12, 方便使用 deepin 的 wine 程序</li>
<li>mate: 桌面环境，比较轻量，实测兼容性比 xfce 好</li>
<li><a href="https://gitee.com/spark-store-project/spark-store">星火应用商店</a>: 方便安装各种国内软件和 Wine 程序</li>
<li>统信 Windows 应用兼容引擎: 在星火应用商店中安装，可以用于安装和打包商店中没有的程序</li>
</ul>
<h2 id="虚拟机配置"><a href="#虚拟机配置" class="headerlink" title="虚拟机配置"></a>虚拟机配置</h2><p>步骤</p>
<ol>
<li>创建好 pve 虚拟机， 安装 debian，选择 mate 桌面</li>
<li>设置分辨率：控制中心 -&gt; 显示器 -&gt; 设置为 1920x1080</li>
<li>关闭自动睡眠：控制中心 -&gt; 电源管理 -&gt; 动作和显示修改为<code>从不</code></li>
<li>关闭屏幕保护：控制中心 -&gt; 屏幕保护程序 -&gt; 取消勾选所有选项</li>
<li>安装<code>gnome-screenshot</code>:  用于 PyAutoGUI 内部调用截图</li>
<li>安装星火应用商店（可选）</li>
<li>安装统信 Windows 应用兼容引擎（可选）：在星火应用商店中安装</li>
<li>火焰截图（可选）：在星火应用商店中安装，更方便对应用的按钮和文字截图。</li>
</ol>
<p>其他说明：</p>
<ul>
<li>如果不关闭自动睡眠和屏幕保护，PyAutoGUI 就无法截取到应用的图像，也就无法操作了。</li>
<li>图形显示协议默认选 x11，wayland 对显卡有要求，虚拟机不方便处理。</li>
</ul>
<h3 id="睡眠后时间不对"><a href="#睡眠后时间不对" class="headerlink" title="睡眠后时间不对"></a>睡眠后时间不对</h3><p>虚拟机睡眠唤醒后时间没有更新，不知道为什么没有触发时间更新。<br>目前只能通过强行同步来解决</p>
<p>在脚本里执行同步命令</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 和阿里云ntp服务器同步</span></span><br><span class="line"><span class="built_in">sudo</span> ntpdig -S ntp.aliyun.com</span><br></pre></td></tr></table></figure>

<p>或者，理论上可以通过虚拟机中的 systemd-suspend hook，或者 pve hookscript 来解决（没试成功）</p>
<h2 id="PyAutoGUI-使用技巧"><a href="#PyAutoGUI-使用技巧" class="headerlink" title="PyAutoGUI 使用技巧"></a>PyAutoGUI 使用技巧</h2><h3 id="远程登录调试"><a href="#远程登录调试" class="headerlink" title="远程登录调试"></a>远程登录调试</h3><p>pve 默认图片控制台不太方便，不能复制粘贴，所以使用远程登录调试<br>由于系统使用 x11 环境，这里使用 xrdp 作为远程服务端。<br>如果以后更新到 wayland，可以使用 <code>gnome-remote-desktop</code>。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install xorgxrdp xrdp</span><br></pre></td></tr></table></figure>

<h3 id="远程登录时找不到显示器"><a href="#远程登录时找不到显示器" class="headerlink" title="远程登录时找不到显示器"></a>远程登录时找不到显示器</h3><p>xrdp 或者 ssh 远程登录执行脚本时，窗口相关命令可能会提示找不到显示器。<br>可以在自动化脚本中设置以下环境变量</p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">os<span class="selector-class">.environ</span><span class="selector-attr">[<span class="string">&#x27;DISPLAY&#x27;</span>]</span> = <span class="string">&#x27;:0&#x27;</span></span><br><span class="line">os<span class="selector-class">.environ</span><span class="selector-attr">[<span class="string">&#x27;XAUTHORITY&#x27;</span>]</span> = <span class="string">&#x27;/home/&lt;your-name&gt;/.Xauthority&#x27;</span></span><br></pre></td></tr></table></figure>

<h3 id="过滤没必要的截图日志"><a href="#过滤没必要的截图日志" class="headerlink" title="过滤没必要的截图日志"></a>过滤没必要的截图日志</h3><p>gnome-screenshot 截图时会产生一些无用日志</p>
<figure class="highlight fsharp"><table><tr><td class="code"><pre><span class="line"><span class="operator">**</span> Message<span class="operator">:</span> 17<span class="operator">:</span>36<span class="operator">:</span>31.888<span class="operator">:</span> Unable <span class="keyword">to</span> <span class="keyword">use</span> GNOME Shell&#x27;s </span><br><span class="line">builtin screenshot <span class="keyword">interface</span>, resorting <span class="keyword">to</span> fallback X11.</span><br></pre></td></tr></table></figure>

<p>新建一个 <code>gnome-screenshot</code> 文件，赋予执行权限，放到 PATH 环境变量中比 <code>/usr/bin</code> 靠前的路径</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">exec</span> /usr/bin/gnome-screenshot <span class="string">&quot;<span class="variable">$@</span>&quot;</span> &gt;&gt; /tmp/gnome-screenshot.log 2&gt;&amp;1</span><br></pre></td></tr></table></figure>

<h3 id="验证码识别"><a href="#验证码识别" class="headerlink" title="验证码识别"></a>验证码识别</h3><h4 id="pytesseract"><a href="#pytesseract" class="headerlink" title="pytesseract"></a>pytesseract</h4><p>常见的方案是 <code>pytesseract</code>， 但是效果不好，识别率比较一般。</p>
<p>安装</p>
<figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line">sudo apt <span class="keyword">install</span> tesseract-ocr</span><br><span class="line">pip <span class="keyword">install</span> pytesseract</span><br></pre></td></tr></table></figure>

<p>使用</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">pytesseract.image_to_string(image, config=<span class="string">&#x27;--psm 8 -c tessedit_char_whitelist=0123456789&#x27;</span>)</span><br></pre></td></tr></table></figure>

<h4 id="ddddocr"><a href="#ddddocr" class="headerlink" title="ddddocr"></a>ddddocr</h4><p><a href="https://github.com/sml2h3/ddddocr">ddddocr</a> 是基于机器学习的验证码识别库，识别效果比较好。</p>
<p>这里使用 docker 安装 <a href="https://github.com/sml2h3/ddddocr-fastapi">fastapi接口</a></p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">docker</span> run -d -p <span class="number">8000</span>:<span class="number">8000</span> oozzbb/ddddocr-fastapi:latest</span><br></pre></td></tr></table></figure>

<p>使用</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">ocr_image</span>(<span class="params">file=<span class="string">&#x27;region.png&#x27;</span></span>):</span><br><span class="line">    url = <span class="string">&#x27;http://your-address:8000/ocr&#x27;</span></span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(file, <span class="string">&#x27;rb&#x27;</span>) <span class="keyword">as</span> fp:</span><br><span class="line">        files = &#123;</span><br><span class="line">            <span class="string">&#x27;file&#x27;</span>: fp,</span><br><span class="line">        &#125;</span><br><span class="line">    data = &#123;</span><br><span class="line">        <span class="string">&#x27;probability&#x27;</span>: <span class="string">&#x27;false&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;png_fix&#x27;</span>: <span class="string">&#x27;false&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;charsets&#x27;</span>: <span class="string">&#x27;0123456789&#x27;</span>  <span class="comment"># 验证码可能的字符列表</span></span><br><span class="line">    &#125;</span><br><span class="line">    response = requests.post(url, files=files, data=data)</span><br><span class="line">    <span class="keyword">assert</span> response.ok</span><br><span class="line">    <span class="keyword">return</span> response.json()[<span class="string">&#x27;data&#x27;</span>]</span><br></pre></td></tr></table></figure>

<h3 id="高效执行自动化脚本"><a href="#高效执行自动化脚本" class="headerlink" title="高效执行自动化脚本"></a>高效执行自动化脚本</h3><p>可以在宿主机远程调用自动化脚本，并且脚本执行完成后挂起虚拟机</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">HOST=your-name@vm-ip-address</span><br><span class="line">VMID=109   <span class="comment"># pve 虚拟机id</span></span><br><span class="line"></span><br><span class="line">qm resume <span class="variable">$VMID</span>   <span class="comment"># 唤醒虚拟机</span></span><br><span class="line"><span class="built_in">sleep</span> 2</span><br><span class="line"></span><br><span class="line">ssh <span class="variable">$HOST</span> <span class="string">&quot;SOME_VAR=foobar python your-script.py&quot;</span>  <span class="comment"># 执行脚本</span></span><br><span class="line"><span class="built_in">sleep</span> 1</span><br><span class="line"></span><br><span class="line">qm <span class="built_in">suspend</span> <span class="variable">$VMID</span>   <span class="comment"># 关闭虚拟机</span></span><br></pre></td></tr></table></figure>

<h3 id="其他有用的函数"><a href="#其他有用的函数" class="headerlink" title="其他有用的函数"></a>其他有用的函数</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 切换窗口到前台</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">wm_to_front</span>(<span class="params">title: <span class="built_in">str</span></span>):</span><br><span class="line">    <span class="keyword">if</span> sys.platform == <span class="string">&#x27;linux&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> subprocess.check_call([<span class="string">&#x27;wmctrl&#x27;</span>, <span class="string">&#x27;-a&#x27;</span>, title])</span><br><span class="line">    <span class="keyword">raise</span> NotImplementedError</span><br><span class="line"></span><br><span class="line"><span class="comment"># 点击或者移到到图片位置</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">click_image</span>(<span class="params">image, confidence=<span class="number">0.9</span>, grayscale=<span class="literal">True</span>, delay=<span class="number">1.5</span>, </span></span><br><span class="line"><span class="params">        duration=<span class="number">0.6</span>, offset=(<span class="params"><span class="number">1</span>, <span class="number">0</span></span>), abort=<span class="literal">True</span>, click=<span class="literal">True</span></span>):</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        loc = pyautogui.locateOnScreen(image, confidence=confidence, grayscale=grayscale)</span><br><span class="line">    <span class="keyword">except</span> pyautogui.ImageNotFoundException:</span><br><span class="line">        loc = <span class="literal">None</span></span><br><span class="line">    <span class="keyword">if</span> loc:</span><br><span class="line">        x, y = pyautogui.center(loc)</span><br><span class="line">        <span class="keyword">if</span> click:</span><br><span class="line">            pyautogui.click(x+offset[<span class="number">0</span>], y+offset[<span class="number">1</span>], duration=duration)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            pyautogui.moveTo(x+offset[<span class="number">0</span>], y+offset[<span class="number">1</span>], duration=duration)</span><br><span class="line">        time.sleep(delay)</span><br><span class="line">        <span class="keyword">return</span> loc</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">if</span> abort:</span><br><span class="line">            <span class="keyword">raise</span> RuntimeError(<span class="string">f&quot;can not find <span class="subst">&#123;image&#125;</span>!&quot;</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 等待某个图片出现</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">wait_button</span>(<span class="params">image, timeout, interval=<span class="number">10</span></span>):</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(timeout//interval):</span><br><span class="line">        time.sleep(interval)</span><br><span class="line">        loc = click_image(image, abort=<span class="literal">False</span>)</span><br><span class="line">        <span class="keyword">if</span> loc:</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">raise</span> RuntimeError(<span class="string">f&#x27;wait <span class="subst">&#123;image&#125;</span> failed&#x27;</span>)</span><br><span class="line">    time.sleep(interval/<span class="number">2</span>)</span><br></pre></td></tr></table></figure>

<h2 id="使用感受"><a href="#使用感受" class="headerlink" title="使用感受"></a>使用感受</h2><p>PyAutoGUI 是基于图像而不是图型控件来识别目标，没有确认反馈。<br>相比于网页自动化的 Selenium 就感觉落后些了。<br>当然，Windows 平台有 <code>pywinauto</code> 支持控件识别，但我不咋熟悉。<br>总之，PyAutoGUI 就像手枪，虽然比较简陋，但还是很直观的，执行简单的任务也够用了。</p>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul>
<li>自动化代码中不要编码用户名密码相关信息，建议用环境变量传入</li>
<li>不要轻易调整系统的 dpi(建议设为1.0) 和分辨率，可能导致图片无法识别</li>
<li>xfce 对 wine 应用的兼容性似乎不好，wine 文件保存对话框被可能被反复触发，原因不明。</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python2迁移Python3指南</title>
    <url>/2020/05/11/python-2to3-guide/</url>
    <content><![CDATA[<h2 id="前置要求"><a href="#前置要求" class="headerlink" title="前置要求"></a>前置要求</h2><ul>
<li>了解Python3和Python2的区别，参考<a href="/2017/08/30/difference-between-python2-and-python3/">Python2和Python3区别</a>，<a href="https://github.com/wjo1212/PythonChinaMeetup2020/blob/master/PythonChinaMeetup-20200510-Python3%E6%96%B0%E7%89%B9%E6%80%A7%E4%BB%8B%E7%BB%8D.pdf">Python3新特性</a></li>
<li>完备的测试用例</li>
<li>必要的工具：future，用于自动修改不兼容的语法。six，用于Python版本判断。pylint，语法检查</li>
<li>做好备份</li>
<li>测试（至关重要），下面的每一步改动都得确保代码行为没有发生变化。</li>
</ul>
<h3 id="future安装"><a href="#future安装" class="headerlink" title="future安装"></a>future安装</h3><p><code>pip install future -U</code></p>
<span id="more"></span>

<h3 id="项目大致结构"><a href="#项目大致结构" class="headerlink" title="项目大致结构"></a>项目大致结构</h3><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">├── ReadMe<span class="selector-class">.md</span></span><br><span class="line">├── requirements<span class="selector-class">.txt</span></span><br><span class="line">├── run<span class="selector-class">.py</span></span><br><span class="line">├── <span class="attribute">src</span></span><br><span class="line">│   ├── __init__<span class="selector-class">.py</span></span><br><span class="line">│   ├── conf<span class="selector-class">.py</span></span><br><span class="line">│   ├── handlers</span><br><span class="line">│   │   ├── __init__<span class="selector-class">.py</span></span><br><span class="line">│   │   └── draw<span class="selector-class">.py</span></span><br><span class="line">│   ├── process</span><br><span class="line">│   │   ├── __init__<span class="selector-class">.py</span></span><br><span class="line">│   │   ├── decision<span class="selector-class">.py</span></span><br><span class="line">│   │   ├── preprocess<span class="selector-class">.py</span></span><br><span class="line">│   │   └── process<span class="selector-class">.py</span></span><br><span class="line">│   ├── service<span class="selector-class">.py</span></span><br><span class="line">│   └── workflow<span class="selector-class">.py</span></span><br><span class="line">├── tests</span><br><span class="line">│   ├── __init__<span class="selector-class">.py</span></span><br><span class="line">│   ├── test_preprocess.py</span><br></pre></td></tr></table></figure>

<h2 id="修改过时的Python2代码"><a href="#修改过时的Python2代码" class="headerlink" title="修改过时的Python2代码"></a>修改过时的Python2代码</h2><p>这一步用于测试的解释器是Python2<br>这一步是避免使用过于古老的Python2语法，将项目代码升级为更现代的Python2代码。</p>
<p>执行: <code>futurize --stage1 -w src tests</code></p>
<p>可能涉及的改动</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 异常处理</span></span><br><span class="line">-    <span class="keyword">except</span> Exception, err:</span><br><span class="line">+    <span class="keyword">except</span> Exception <span class="keyword">as</span> err:</span><br><span class="line"></span><br><span class="line"><span class="comment"># 字典元素判断</span></span><br><span class="line">- <span class="keyword">if</span> lr_space_dict.has_key(<span class="built_in">str</span>(feature)):</span><br><span class="line">+ <span class="keyword">if</span> <span class="built_in">str</span>(feature) <span class="keyword">in</span> lr_space_dict:</span><br><span class="line"></span><br><span class="line"><span class="comment"># import方式改变</span></span><br><span class="line">- <span class="keyword">from</span> workflow <span class="keyword">import</span> WorkFlow</span><br><span class="line">+ <span class="keyword">from</span> .workflow <span class="keyword">import</span> WorkFlow</span><br><span class="line"></span><br><span class="line"><span class="comment"># print</span></span><br><span class="line">- <span class="built_in">print</span> <span class="string">&#x27;load model %s&#x27;</span> % <span class="variable language_">self</span>.name</span><br><span class="line">+ <span class="built_in">print</span>(<span class="string">&#x27;load model %s&#x27;</span> % <span class="variable language_">self</span>.name)</span><br></pre></td></tr></table></figure>

<h2 id="代码升级Python3，-并添加Python2支持"><a href="#代码升级Python3，-并添加Python2支持" class="headerlink" title="代码升级Python3， 并添加Python2支持"></a>代码升级Python3， 并添加Python2支持</h2><p>这一步用于测试的解释器是Python2<br>一般在升级过程中，不直接移除Python2支持，否则一旦发现问题难以回滚。<br>也不方便确认，代码改动是否产生了非预期的变化。</p>
<p>执行：<code>futurize --stage2 -x libfuturize.fixes.fix_unicode_keep_u -w src tests</code></p>
<p><strong>注意</strong>：由于futurize关于unicode的处理存在一些bug，所以字符编码的我们自己单独处理（见后文）。这里的<code>-x libfuturize.fixes.fix_unicode_keep_u</code>参数，跳过对unicode的自动处理，不将代码中的unicode替换为str。同时，我们要逐一去除文件中自动添加的<code>from builtins import str</code>，避免类似<code>import unicode as str</code>的行为。</p>
<p>可能涉及的改动:</p>
<ol>
<li>迭代器对象相关改动</li>
<li>内置库重命名相关改动</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># map() -&gt; list(map())</span></span><br><span class="line">- year1, month1, day1 = <span class="built_in">map</span>(<span class="built_in">int</span>, date_created.split(<span class="string">&#x27; &#x27;</span>)[<span class="number">0</span>].split(<span class="string">&#x27;-&#x27;</span>))</span><br><span class="line">+ year1, month1, day1 = <span class="built_in">list</span>(<span class="built_in">map</span>(<span class="built_in">int</span>, date_created.split(<span class="string">&#x27; &#x27;</span>)[<span class="number">0</span>].split(<span class="string">&#x27;-&#x27;</span>)))</span><br><span class="line"></span><br><span class="line"><span class="comment"># dict.keys() - list(dict.keys())</span></span><br><span class="line">- <span class="keyword">for</span> city <span class="keyword">in</span> city_dict.keys():</span><br><span class="line">+ <span class="keyword">for</span> city <span class="keyword">in</span> <span class="built_in">list</span>(city_dict.keys()):</span><br><span class="line"></span><br><span class="line"><span class="comment"># dict.iteritems() -&gt; dict.items()</span></span><br><span class="line">- <span class="keyword">for</span> k, v <span class="keyword">in</span> hawk_info.iteritems():</span><br><span class="line">+ <span class="keyword">for</span> k, v <span class="keyword">in</span> hawk_info.items():</span><br><span class="line"> </span><br><span class="line"><span class="comment"># dict.items() -&gt; list(dict.iteritems())</span></span><br><span class="line">- <span class="keyword">for</span> name, val <span class="keyword">in</span> trans_td_schema.items():</span><br><span class="line">+ <span class="keyword">for</span> name, val <span class="keyword">in</span> <span class="built_in">list</span>(trans_td_schema.items()):</span><br><span class="line"></span><br><span class="line"><span class="comment"># xrange -&gt; list(range)</span></span><br><span class="line">- <span class="keyword">for</span> index <span class="keyword">in</span> xrange(<span class="built_in">len</span>(rule_files)):</span><br><span class="line">+ <span class="keyword">for</span> index <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(rule_files)):</span><br><span class="line"></span><br><span class="line"><span class="comment"># ConfigParser -&gt; configparser</span></span><br><span class="line">- <span class="keyword">import</span> ConfigParser</span><br><span class="line">+ <span class="keyword">from</span> future <span class="keyword">import</span> standard_library</span><br><span class="line">+ standard_library.install_aliases()</span><br><span class="line">+ <span class="keyword">import</span> configparser</span><br></pre></td></tr></table></figure>

<h2 id="Python文本处理改为unicode"><a href="#Python文本处理改为unicode" class="headerlink" title="Python文本处理改为unicode"></a>Python文本处理改为unicode</h2><p>这一步用于测试的解释器是Python2和Python3</p>
<p>正确处理字符的原则：</p>
<ol>
<li>程序内部均使用unicode，所以大部分业务代码不需要进行编码处理。</li>
<li>对输入的二进制数据（文本，网络包等），使用对应的字符集进行decode(), 转为unicode</li>
<li>将内部数据encode后进行传输存储</li>
</ol>
<p>也就是：<code>外部数据（字节）-&gt; decode -&gt; unicode -&gt; encode -&gt; 输出数据（字节</code></p>
<h3 id="启用默认unicode支持"><a href="#启用默认unicode支持" class="headerlink" title="启用默认unicode支持"></a>启用默认unicode支持</h3><p>启用默认unicode支持：<code>futurize --stage1 --unicode-literals src tests -w</code></p>
<p>涉及的改动:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 所有文件的头部会增加下面的语句，作用是将源码中的所有字符串视作unicode</span></span><br><span class="line"><span class="comment"># 也就是 &quot;中&quot; 会等效于 u&quot;中&quot;， 不需要`u`作为unicode的前缀 </span></span><br><span class="line">+ <span class="keyword">from</span> __future__ <span class="keyword">import</span> unicode_literals</span><br></pre></td></tr></table></figure>

<h3 id="修改编码相关的代码"><a href="#修改编码相关的代码" class="headerlink" title="修改编码相关的代码"></a>修改编码相关的代码</h3><ol>
<li>str() -&gt; unicode()</li>
<li>open -&gt; io.open</li>
<li>去除程序内部多余的encode，decode</li>
<li>redis, requests等库，增加必要的decode代码</li>
</ol>
<p>涉及的改动:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># str() -&gt; unicode()</span></span><br><span class="line">- feature = <span class="built_in">str</span>(feature)</span><br><span class="line">+ feature = unicode(feature)</span><br><span class="line"></span><br><span class="line"><span class="comment"># open -&gt; io.open</span></span><br><span class="line">- <span class="keyword">with</span> <span class="built_in">open</span>(v) <span class="keyword">as</span> f:</span><br><span class="line">+ <span class="keyword">with</span> io.<span class="built_in">open</span>(v, encoding=<span class="string">&#x27;utf8&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line"></span><br><span class="line"><span class="comment"># redis增加必要的decode代码</span></span><br><span class="line">-     <span class="keyword">return</span> <span class="variable language_">self</span>.connection.get(key)</span><br><span class="line">+     res = <span class="variable language_">self</span>.connection.get(key)</span><br><span class="line">+     <span class="keyword">return</span> res.decode(<span class="string">&#x27;utf8&#x27;</span>) <span class="keyword">if</span> res <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> <span class="keyword">else</span> <span class="literal">None</span></span><br></pre></td></tr></table></figure>

<h3 id="为Python3添加unicode函数"><a href="#为Python3添加unicode函数" class="headerlink" title="为Python3添加unicode函数"></a>为Python3添加unicode函数</h3><p>为了代码在Python2和Python3都正确运行，必须给Python增加unicode函数。</p>
<p>如果后面代码不需要Python2支持，则这一步的改动可以去除，并且把所有的unicode调用改为str即可。</p>
<p>实现</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> six</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> six.PY2:</span><br><span class="line">    <span class="keyword">from</span> __builtin__ <span class="keyword">import</span> unicode</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">unicode</span>(<span class="title class_ inherited__">str</span>):</span><br><span class="line">        <span class="keyword">def</span> <span class="title function_">__new__</span>(<span class="params">cls, unicode_or_bytes=<span class="string">&#x27;&#x27;</span></span>):</span><br><span class="line">            <span class="keyword">if</span> <span class="built_in">isinstance</span>(unicode_or_bytes, <span class="built_in">bytes</span>):</span><br><span class="line">                <span class="keyword">return</span> <span class="built_in">str</span>.__new__(cls, unicode_or_bytes, encoding=<span class="string">&#x27;utf8&#x27;</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">str</span>.__new__(cls, unicode_or_bytes)</span><br></pre></td></tr></table></figure>

<h2 id="修复于解释器版本相关的行为"><a href="#修复于解释器版本相关的行为" class="headerlink" title="修复于解释器版本相关的行为"></a>修复于解释器版本相关的行为</h2><p>这一步用于测试的解释器是Python2和Python3</p>
<h3 id="字典-集合遍历顺序"><a href="#字典-集合遍历顺序" class="headerlink" title="字典&#x2F;集合遍历顺序"></a>字典&#x2F;集合遍历顺序</h3><p>python的字典遍历是不保证顺序的，不同版本解释器遍历顺序可能不同。<br>如果你的代码对遍历顺序有依赖，建议固定遍历顺序，可以使用OrderedDict，或者遍历前排序，或者指定遍历的key。</p>
<h3 id="round"><a href="#round" class="headerlink" title="round"></a>round</h3><p>Python2和Python3的四舍五入行为不一样。<br>如果有数值处理相关的代码，建议做如下修复.</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">python2round</span>(<span class="params">number, ndigits=<span class="number">0</span></span>):</span><br><span class="line">    <span class="keyword">if</span> sys.version_info[<span class="number">0</span>] == <span class="number">2</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">round</span>(number, ndigits)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">from</span> decimal <span class="keyword">import</span> Decimal, ROUND_HALF_UP</span><br><span class="line">    res = Decimal.from_float(number).quantize(Decimal(<span class="number">10</span>) ** -ndigits, rounding=ROUND_HALF_UP)</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">float</span>(res)</span><br></pre></td></tr></table></figure>

<h2 id="大功告成"><a href="#大功告成" class="headerlink" title="大功告成"></a>大功告成</h2><p>至此，Python2到Python3的迁移已然完成，你获得了支持python3和python2的代码。</p>
<p>值得小酌一杯</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://python-future.org/automatic_conversion.html">https://python-future.org/automatic_conversion.html</a></li>
<li><a href="https://github.com/PythonCharmers/python-future">https://github.com/PythonCharmers/python-future</a></li>
<li><a href="https://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior#answer-10826537">https://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior#answer-10826537</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>2to3</tag>
      </tags>
  </entry>
  <entry>
    <title>使用SWIG实现Python调用c++</title>
    <url>/2020/08/05/python-connect-cpp-programs-with-SWIG/</url>
    <content><![CDATA[<p> <strong><a href="http://www.swig.org/">SWIG</a></strong> (Simplified Wrapper and Interface Generator) 是一个开源工具，用于让C&#x2F;C++的程序和库可以被其他语言调用。</p>
<p>支持的非常多语言，有Lua, Perl, PHP, Python, R, Ruby, C#, Java, JavaScript, Go, Scheme 等。</p>
<span id="more"></span>
<h2 id="大致流程"><a href="#大致流程" class="headerlink" title="大致流程"></a>大致流程</h2><ol>
<li>定义SWIG接口文件</li>
<li>生成C&#x2F;C++和目标语言的包装代码</li>
<li>编译位置无关的C&#x2F;C++包装代码和功能代码，并链接为动态库</li>
</ol>
<p><img data-src="https://image.ponder.work/mweb/2020-08-06-Untitled%20Diagram.svg"></p>
<h2 id="c-示例代码"><a href="#c-示例代码" class="headerlink" title="c++示例代码"></a>c++示例代码</h2><p>下面用到的C++代码</p>
<p>头文件</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* utils.h */</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> _H_UTILS_H_</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> _H_UTILS_H_</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sstream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Utils</span> &#123;</span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Utils</span>();</span><br><span class="line">    <span class="function">string <span class="title">Float2String</span><span class="params">(<span class="type">float</span> Num)</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure>

<p>具体实现代码</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* utils.cpp */</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;utils.h&quot;</span></span></span><br><span class="line"></span><br><span class="line">Utils::<span class="built_in">Utils</span>() &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">string <span class="title">Utils::Float2String</span><span class="params">(<span class="type">float</span> Num)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	ostringstream oss;</span><br><span class="line">	oss&lt;&lt;Num;</span><br><span class="line">	<span class="function">string <span class="title">str</span><span class="params">(oss.str())</span></span>;</span><br><span class="line">	<span class="keyword">return</span> str;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="SWIG-接口文件"><a href="#SWIG-接口文件" class="headerlink" title="SWIG 接口文件"></a>SWIG 接口文件</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"> <span class="comment">/* example.i */</span></span><br><span class="line"> %module model_engine</span><br><span class="line"> %include <span class="string">&quot;std_string.i&quot;</span></span><br><span class="line"> %&#123;</span><br><span class="line"> <span class="comment">/* Put header files here or function declarations like below */</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;utils.h&quot;</span></span></span><br><span class="line"> %&#125;</span><br><span class="line"></span><br><span class="line">%include <span class="string">&quot;utils.h&quot;</span></span><br></pre></td></tr></table></figure>

<p>其中<code>%include &quot;std_string.i&quot;</code>, 实现了c++的string自动转python的str</p>
<h2 id="生成动态库"><a href="#生成动态库" class="headerlink" title="生成动态库"></a>生成动态库</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">swig -c++ -python  example.i</span><br><span class="line">g++ -O2 -fPIC -std=c++0x -c utils.cpp</span><br><span class="line">g++ -O2 -fPIC -std=c++0x -c example_wrap.cxx -I/usr/include/python3.8/</span><br><span class="line">g++ -shared utils.o example_wrap.o -o _example.so</span><br></pre></td></tr></table></figure>

<p>注意编译<code>example_wrap.cxx</code>时引入对应版本的python头文件，不同系统的路径可能有所不同。</p>
<h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">import</span> example</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>ul = example.Utils()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>ul.Float2String(<span class="number">1.111</span>)</span><br><span class="line"><span class="string">&#x27;1.111&#x27;</span></span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://segmentfault.com/a/1190000013219667">https://segmentfault.com/a/1190000013219667</a></li>
<li><a href="http://www.swig.org/Doc4.0/SWIGDocumentation.html#Python">http://www.swig.org/Doc4.0/SWIGDocumentation.html#Python</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>C++</tag>
        <tag>SWIG</tag>
      </tags>
  </entry>
  <entry>
    <title>循环import导致的模块被多次import</title>
    <url>/2020/06/20/python-import-behavior-and-circular-import/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>循环import是很多Python初学者都会遇到问题，网上有也有很多文章讲解决方法，比如<a href="https://www.jianshu.com/p/a1e91cc53b07">这篇</a>，不清楚的可以自行查阅，这里就不赘述了。</p>
<p>那么，为啥老司机也会遇到这个问题呢？这段时间一直在搞把redis复刻一个python版本，在复刻代码时就遇到了这个问题。而且我也使用了延迟import，却没能解决。</p>
<p>下面我们来详细分析下</p>
<span id="more"></span>

<h2 id="症状"><a href="#症状" class="headerlink" title="症状"></a>症状</h2><p>先看两段代码</p>
<p>run.py</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line">logging.basicConfig(level=<span class="string">&#x27;DEBUG&#x27;</span>, </span><br><span class="line">    <span class="built_in">format</span>=<span class="string">&#x27;[&#123;asctime&#125; &#123;module&#125;.&#123;funcName:&lt;11&#125;] &#123;message&#125;&#x27;</span>, style=<span class="string">&#x27;&#123;&#x27;</span>)</span><br><span class="line">logging.info(sys.modules[<span class="string">&#x27;__main__&#x27;</span>])</span><br><span class="line">logging.info(<span class="string">&#x27;begin load&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Server</span>:</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">server = Server()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">start</span>():</span><br><span class="line">    <span class="keyword">from</span> foo <span class="keyword">import</span> do_someting</span><br><span class="line">    logging.info(<span class="string">&#x27;call&#x27;</span>)</span><br><span class="line">    <span class="keyword">assert</span> <span class="keyword">not</span> <span class="built_in">hasattr</span>(server, <span class="string">&#x27;name&#x27;</span>)</span><br><span class="line">    server.name = <span class="string">&#x27;aaa&#x27;</span></span><br><span class="line">    logging.info(<span class="built_in">repr</span>(server))</span><br><span class="line">    logging.info(<span class="string">&#x27;%s\t%s&#x27;</span>, <span class="built_in">repr</span>(Server), <span class="built_in">id</span>(Server))</span><br><span class="line">    do_someting()</span><br><span class="line"></span><br><span class="line">logging.info(<span class="string">&#x27;end load&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    start()</span><br></pre></td></tr></table></figure>

<p>foo.py</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line">logging.info(<span class="string">&#x27;begin load&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">do_someting</span>():</span><br><span class="line">    logging.info(<span class="string">&#x27;begin call&#x27;</span>)</span><br><span class="line">    <span class="keyword">import</span> run</span><br><span class="line"></span><br><span class="line">    logging.info(<span class="built_in">repr</span>(run.server))</span><br><span class="line">    logging.info(<span class="string">&#x27;%s\t%s&#x27;</span>, (run.Server), <span class="built_in">id</span>(run.Server))</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">hasattr</span>(run.server, <span class="string">&#x27;name&#x27;</span>):</span><br><span class="line">        logging.info(<span class="string">&#x27;found attr name&#x27;</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        logging.info(<span class="string">&#x27;not found attr name&#x27;</span>)</span><br><span class="line">    logging.info(<span class="string">&#x27;end call&#x27;</span>)</span><br><span class="line"></span><br><span class="line">logging.info(<span class="string">&#x27;end load&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>再看执行<code>python run.py</code>的结果</p>
<figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">659</span> run.&lt;module&gt;   ] &lt;module <span class="string">&#x27;__main__&#x27;</span> <span class="keyword">from</span> <span class="string">&#x27;/Volumes/study/Projects/code_snippet/circular_import/run.py&#x27;</span>&gt;</span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">659</span> run.&lt;module&gt;   ] <span class="keyword">begin</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">659</span> run.&lt;module&gt;   ] <span class="keyword">end</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">662</span> foo.&lt;module&gt;   ] <span class="keyword">begin</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">662</span> foo.&lt;module&gt;   ] <span class="keyword">end</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">662</span> run.<span class="keyword">start</span>      ] <span class="keyword">call</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">662</span> run.<span class="keyword">start</span>      ] &lt;__main__.<span class="keyword">Server</span> <span class="keyword">object</span> at <span class="number">0x1064eb940</span>&gt;</span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">662</span> run.<span class="keyword">start</span>      ] &lt;<span class="keyword">class</span> <span class="string">&#x27;__main__.Server&#x27;</span>&gt; <span class="number">140250245614784</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">662</span> foo.do_someting] <span class="keyword">begin</span> <span class="keyword">call</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">663</span> run.&lt;module&gt;   ] &lt;module <span class="string">&#x27;__main__&#x27;</span> <span class="keyword">from</span> <span class="string">&#x27;/Volumes/study/Projects/code_snippet/circular_import/run.py&#x27;</span>&gt;</span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">663</span> run.&lt;module&gt;   ] <span class="keyword">begin</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">663</span> run.&lt;module&gt;   ] <span class="keyword">end</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">663</span> foo.do_someting] &lt;run.<span class="keyword">Server</span> <span class="keyword">object</span> at <span class="number">0x1065d8b50</span>&gt;</span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">663</span> foo.do_someting] &lt;<span class="keyword">class</span> <span class="string">&#x27;run.Server&#x27;</span>&gt;  <span class="number">140250247610512</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">663</span> foo.do_someting] <span class="keyword">not</span> <span class="built_in">found</span> attr <span class="type">name</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">663</span> foo.do_someting] <span class="keyword">end</span> <span class="keyword">call</span></span><br></pre></td></tr></table></figure>

<p>这里已经用延迟导入，这个典型方法，解决了执行时报错的问题</p>
<p>但是，还是可以发现几个问题</p>
<ol>
<li>run.py 被加载了两次</li>
<li>在run模块中的server实例和Server类，与foo模块中的id一样，也就是不是同一个对象。（第8行和14行）</li>
</ol>
<h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>先复习下import机制</p>
<p>import 语句结合了两个操作；它先搜索指定名称的模块，然后将搜索结果绑定到当前作用域中的名称。 import 语句的搜索操作定义为对 <code>__import__()</code> 函数的调用并带有适当的参数。 <code>__import__()</code> 的返回值会被用于执行 import 语句的名称绑定操作。 </p>
<p>对 <code>__import__()</code> 的直接调用将仅执行模块搜索以及在找到时的模块创建操作。 不过也可能产生某些副作用，例如导入父包和更新各种缓存 (包括 sys.modules)，只有 import 语句会执行名称绑定操作。</p>
<p><code>sys.modules</code>是一个字典，缓存了已加载的模型，以模块名称为key，模块对象为value。<br>执行import 语句时，先在<code>sys.modules</code>缓存中查询该模块，如已存在者返回该对象，否则从文件系统中加载该模块。</p>
<figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">10</span>:<span class="number">57</span>:<span class="number">03</span>,<span class="number">662</span> run.<span class="keyword">start</span>      ] &lt;__main__.<span class="keyword">Server</span> <span class="keyword">object</span> at <span class="number">0x1064eb940</span>&gt;</span><br></pre></td></tr></table></figure>
<p>从上面的这行输出可以看出，当run作为程序入口时，模块名称变为了<code>__main__</code>, 查看 <code>sys.modules</code>，也只发现了<code>__main__</code>，没有发现<code>run</code>.<br>所以， 当<code>do_someting</code> import <code>run</code> 模块时，肯定是发现没有加载，最终导致加载了两次，Server类id不一致也可以理解了。</p>
<h2 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h2><p>所以只要能从<code>sys.modules</code>正确地找到run模块，问题就可以解决。</p>
<p>具体来说有三种方法</p>
<h3 id="方法A"><a href="#方法A" class="headerlink" title="方法A"></a>方法A</h3><p>修改foo.py, 把<code>import run</code>改为<code>import __main__ as run</code></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line">logging.info(<span class="string">&#x27;begin load&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">do_someting</span>():</span><br><span class="line">    logging.info(<span class="string">&#x27;begin call&#x27;</span>)</span><br><span class="line">    <span class="keyword">import</span> __main__ <span class="keyword">as</span> run</span><br><span class="line"></span><br><span class="line">    logging.info(<span class="built_in">repr</span>(run.server))</span><br><span class="line">    logging.info(<span class="string">&#x27;%s\t%s&#x27;</span>, (run.Server), <span class="built_in">id</span>(run.Server))</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">hasattr</span>(run.server, <span class="string">&#x27;name&#x27;</span>):</span><br><span class="line">        logging.info(<span class="string">&#x27;found attr name&#x27;</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        logging.info(<span class="string">&#x27;not found attr name&#x27;</span>)</span><br><span class="line">    logging.info(<span class="string">&#x27;end call&#x27;</span>)</span><br><span class="line"></span><br><span class="line">logging.info(<span class="string">&#x27;end load&#x27;</span>)</span><br></pre></td></tr></table></figure>

<h3 id="方法B"><a href="#方法B" class="headerlink" title="方法B"></a>方法B</h3><p>修改<code>sys.modules</code>，增加key<code>run</code>，指向<code>__main__</code>模块</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">sys.modules[<span class="string">&#x27;run&#x27;</span>] = sys.modules[<span class="string">&#x27;__main__&#x27;</span>]</span><br></pre></td></tr></table></figure>

<h3 id="方法C（推荐）"><a href="#方法C（推荐）" class="headerlink" title="方法C（推荐）"></a>方法C（推荐）</h3><p>启动文件单独使用一个文件，里面不包含其他代码。<br>这时<code>__main__</code>模块变成了bar, 这时run模块的名称就不会改变了，import行为也就正常了</p>
<p>bar.py</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> run <span class="keyword">import</span> start</span><br><span class="line"></span><br><span class="line">start()</span><br></pre></td></tr></table></figure>

<h3 id="输出结果"><a href="#输出结果" class="headerlink" title="输出结果"></a>输出结果</h3><p>上面三种方法，殊途同归，结果都是一样的。</p>
<figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">508</span> run.&lt;module&gt;   ] <span class="keyword">begin</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">508</span> run.&lt;module&gt;   ] <span class="keyword">end</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> foo.&lt;module&gt;   ] <span class="keyword">begin</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> foo.&lt;module&gt;   ] <span class="keyword">end</span> <span class="keyword">load</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> run.<span class="keyword">start</span>      ] <span class="keyword">call</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> run.<span class="keyword">start</span>      ] &lt;run.<span class="keyword">Server</span> <span class="keyword">object</span> at <span class="number">0x10f542fa0</span>&gt;</span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> run.<span class="keyword">start</span>      ] &lt;<span class="keyword">class</span> <span class="string">&#x27;run.Server&#x27;</span>&gt;  <span class="number">140660950954304</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> foo.do_someting] <span class="keyword">begin</span> <span class="keyword">call</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> foo.do_someting] &lt;run.<span class="keyword">Server</span> <span class="keyword">object</span> at <span class="number">0x10f542fa0</span>&gt;</span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> foo.do_someting] &lt;<span class="keyword">class</span> <span class="string">&#x27;run.Server&#x27;</span>&gt;  <span class="number">140660950954304</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> foo.do_someting] <span class="built_in">found</span> attr <span class="type">name</span></span><br><span class="line">[<span class="number">2020</span><span class="number">-06</span><span class="number">-20</span> <span class="number">11</span>:<span class="number">43</span>:<span class="number">31</span>,<span class="number">509</span> foo.do_someting] <span class="keyword">end</span> <span class="keyword">call</span></span><br></pre></td></tr></table></figure>

<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>因为C是编译型语言，可以理解为模块的导入在编译期就完成了，也就不会出现模块的循环依赖，而且全局对象的内存位置也在编译期就固定了。</p>
<p>而Python作为解释型语言，模块的导入加载和执行是混在一起的，所有对象都是可以更改的，也就容易出现这种问题。</p>
<p>切记：</p>
<p><strong>复杂Python程序的入口文件最好保持单一的文件，不要混入其他对象定义，谨慎使用<code>if __name__ == &#39;__main__&#39;</code>写法。</strong></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://docs.python.org/zh-cn/3/reference/import.html">https://docs.python.org/zh-cn/3/reference/import.html</a></li>
<li><a href="https://docs.python.org/zh-cn/3/library/sys.html#sys.modules">https://docs.python.org/zh-cn/3/library/sys.html?#sys.modules</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python不完全指北</title>
    <url>/2018/03/31/python-learning-guide/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>从大学开始接触Python，到现在也差不多四年了，也算小有所成。期间也有很多人问我如何学习Python，也只是零散地回答，刚好最近要做个Python的分享，就将这一块东西整理一下。</p>
<p><strong>本文作为Python学习的指路文章，是个人在Python学习过程中的经验总结。<br>阅读的时候不需要太细致，略读即可。<br>读完后，能够对Python学习的各方面有个大致的概念，在学习过程少踩一些坑，收获就很大了。</strong></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Python（音：派森），是一种强类型的动态语言，由吉多·范罗苏姆 创造，第一版发布于 1991 年。</p>
<h3 id="名称由来"><a href="#名称由来" class="headerlink" title="名称由来"></a>名称由来</h3><p>Python的创始人为吉多·范罗苏姆。1989年的圣诞节期间，吉多·范罗苏姆为了在阿姆斯特丹打发时间，决心开发一个新的脚本解释程序，作为ABC语言的一种继承。之所以选中Python作为程序的名字，是因为他是BBC电视剧——蒙提·派森的飞行马戏团的爱好者。</p>
<span id="more"></span>

<h3 id="创始人"><a href="#创始人" class="headerlink" title="创始人"></a>创始人</h3><p>吉多·范罗苏姆（荷兰语：Guido van Rossum，1956年1月31日－），生于荷兰哈勒姆，计算机程序员，为Python程序设计语言的最初设计者及主要架构师。</p>
<p>在Python社区，吉多·范罗苏姆被人们认为是“仁慈的独裁者”（BDFL），意思是他仍然关注Python的开发进程，并在必要的时刻做出决定。</p>
<p>1982年，在阿姆斯特丹大学获得数学和计算机科学硕士学位，后来他在多个研究机构工作。<br>2002年，获得由自由软件基金会颁发的2001年自由软件进步奖。<br>2003年，获得了荷兰UNIX用户小组奖。<br>2006年，被美国计算机协会（ACM）认定为著名工程师。<br>2005年，加入Google。他用Python语言为Google写了面向网页的代码浏览工具Mondrian，之后又开发了Rietveld。在那里他把一半的时间用来维护Python的开发。<br>2012年，Dropbox宣布吉多·范罗苏姆加入Dropbox公司。</p>
<h3 id="设计哲学"><a href="#设计哲学" class="headerlink" title="设计哲学"></a>设计哲学</h3><p>Python的设计哲学是“优雅”、“明确”、“简单”。</p>
<p>Python开发者的哲学是“用一种方法，最好是只有一种方法来做一件事”，也因此它和拥有明显个人风格的其他语言很不一样。</p>
<p>在设计Python语言时，如果面临多种选择，Python开发者一般会拒绝花俏的语法，而选择明确没有或者很少有歧义的语法。</p>
<p>这些准则被称为“Python格言”。在Python解释器内运行import this可以获得完整的列表。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">import</span> this</span><br><span class="line">The Zen of Python, by Tim Peters</span><br><span class="line"></span><br><span class="line">Beautiful <span class="keyword">is</span> better than ugly.</span><br><span class="line">Explicit <span class="keyword">is</span> better than implicit.</span><br><span class="line">Simple <span class="keyword">is</span> better than <span class="built_in">complex</span>.</span><br><span class="line">Complex <span class="keyword">is</span> better than complicated.</span><br><span class="line">Flat <span class="keyword">is</span> better than nested.</span><br><span class="line">Sparse <span class="keyword">is</span> better than dense.</span><br><span class="line">Readability counts.</span><br><span class="line">Special cases aren<span class="string">&#x27;t special enough to break the rules.</span></span><br><span class="line"><span class="string">Although practicality beats purity.</span></span><br><span class="line"><span class="string">Errors should never pass silently.</span></span><br><span class="line"><span class="string">Unless explicitly silenced.</span></span><br><span class="line"><span class="string">In the face of ambiguity, refuse the temptation to guess.</span></span><br><span class="line"><span class="string">There should be one-- and preferably only one --obvious way to do it.</span></span><br><span class="line"><span class="string">Although that way may not be obvious at first unless you&#x27;</span>re Dutch.</span><br><span class="line">Now <span class="keyword">is</span> better than never.</span><br><span class="line">Although never <span class="keyword">is</span> often better than *right* now.</span><br><span class="line">If the implementation <span class="keyword">is</span> hard to explain, it<span class="string">&#x27;s a bad idea.</span></span><br><span class="line"><span class="string">If the implementation is easy to explain, it may be a good idea.</span></span><br><span class="line"><span class="string">Namespaces are one honking great idea -- let&#x27;</span>s do more of those!</span><br><span class="line"></span><br><span class="line">Python之禅 by Tim Peters</span><br><span class="line"></span><br><span class="line">优美胜于丑陋（Python 以编写优美的代码为目标）</span><br><span class="line">明了胜于晦涩（优美的代码应当是明了的，命名规范，风格相似）</span><br><span class="line">简洁胜于复杂（优美的代码应当是简洁的，不要有复杂的内部实现）</span><br><span class="line">复杂胜于凌乱（如果复杂不可避免，那代码间也不能有难懂的关系，要保持接口简洁）</span><br><span class="line">扁平胜于嵌套（优美的代码应当是扁平的，不能有太多的嵌套）</span><br><span class="line">间隔胜于紧凑（优美的代码有适当的间隔，不要奢望一行代码解决问题）</span><br><span class="line">可读性很重要（优美的代码是可读的）</span><br><span class="line">即便假借特例的实用性之名，也不可违背这些规则（这些规则至高无上）</span><br><span class="line">不要包容所有错误，除非你确定需要这样做（精准地捕获异常，不写 <span class="keyword">except</span>:<span class="keyword">pass</span> 风格的代码）</span><br><span class="line">当存在多种可能，不要尝试去猜测</span><br><span class="line">而是尽量找一种，最好是唯一一种明显的解决方案（如果不确定，就用穷举法）</span><br><span class="line">虽然这并不容易，因为你不是 Python 之父（这里的 Dutch 是指 Guido ）</span><br><span class="line">也许好过不做，但不假思索就动手还不如不做（动手之前要细思量）</span><br><span class="line">如果你无法向人描述你的方案，那肯定不是一个好方案；反之亦然（方案测评标准）</span><br><span class="line">命名空间是一种绝妙的理念，我们应当多加利用（倡导与号召）</span><br></pre></td></tr></table></figure>

<h2 id="特色"><a href="#特色" class="headerlink" title="特色"></a>特色</h2><h3 id="简单"><a href="#简单" class="headerlink" title="简单"></a>简单</h3><p>Python 是一门简单且简约的语言。阅读一份优秀的 Python 程序代码就如同在阅读英语文章一样，尽管这门英语要求十分严格！</p>
<p>Python 这种伪代码式的特质正是它的一大优势。它能够让你专注于解决问题的方案，而不是语言本身。</p>
<h3 id="跨平台性"><a href="#跨平台性" class="headerlink" title="跨平台性"></a>跨平台性</h3><p>由于其开放源码的特性，Python 已被移植到其它诸多平台。</p>
<p>如果你小心地避开了所有系统依赖型的特性, 你所有的 Python 程序可以在其中任何一个平台上工作，不必作出任何改动。</p>
<p>你可以在 GNU&#x2F;Linux、Windows、FreeBSD、Macintosh、 Solaris、 OS&#x2F;2、 Andorid 等等平台上运行 Python！</p>
<h3 id="解释性"><a href="#解释性" class="headerlink" title="解释性"></a>解释性</h3><p>有关这一特性，需要一些详细的解释。</p>
<p>Python 不需要将其编译成二进制码, 你只需要直接从源代码运行该程序。</p>
<p>这使得 Python 程序更便携且易于迁移，你只需要将 Python 程序拷贝到另一台电脑便可让它立即开始工作！</p>
<h3 id="面向对象"><a href="#面向对象" class="headerlink" title="面向对象"></a>面向对象</h3><p>Python 同时支持面向过程编程与面向对象编程。</p>
<p>在面向过程的编程语言中，程序是由仅仅带有可重用特性的子程序与函数所构建起来的。</p>
<p>在面向对象的编程语言中，程序是由结合了数据与功能的对象所构建起来的。</p>
<p>与 C++ 或 Java 这些大型语言相比，Python 具有其特别的、功能强大又简单的方式来实现面向对象编程, 而且<code>万物皆对象</code>。</p>
<h3 id="胶水语言"><a href="#胶水语言" class="headerlink" title="胶水语言"></a>胶水语言</h3><p>Python 很容易和其他语言一同开发, 从而享受到各种不同语言的便利。</p>
<p>如果你需要代码的某一重要部分能够快速地运行，或希望算法的某些部分不被公开，你可以在 C 或 C++ 语言中编写这些程序，然后再将其运用于你的 Python 程序中。</p>
<p>同时，你也可以在你的 C 或 C++ 程序中嵌入 Python，从而向你的程序用户提供脚本功能。</p>
<h3 id="丰富的库"><a href="#丰富的库" class="headerlink" title="丰富的库"></a>丰富的库</h3><p>Python 标准库的规模非常庞大, 可谓<code>自带军需</code>。</p>
<p>它能够帮助你完成诸多事情，包括正则表达式、文档生成、单元测试、多线程、数据库、网页浏览器、CGI、FTP、邮件、XML、XML-RPC、HTML、WAV 文件、密码系统、GUI，以及其它系统依赖型的活动。</p>
<p>除了标准库以外，你还可以在 <a href="https://pypi.org/">PYPI（Python Package Index）</a> 中发掘许多其它高质量的库。</p>
<h2 id="语法简介"><a href="#语法简介" class="headerlink" title="语法简介"></a>语法简介</h2><h3 id="基本数据类型"><a href="#基本数据类型" class="headerlink" title="基本数据类型"></a>基本数据类型</h3><ol>
<li><p>string 字符串<br>一个由字符组成的不可更改的有序列。又单引号或双引号包围，如: <code>&quot;strings&quot;</code>。</p>
</li>
<li><p>byte 字节串<br>一个由字节组成的不可更改的有序列。常用来表示二进制数据，如: <code>b&quot;bytes&quot;</code></p>
</li>
<li><p>float 浮点数<br>浮点数， 精度与系统相关， 不区分单精度双精度。如: <code>3.1415926</code></p>
</li>
<li><p>int 整数<br>整数，不区分是否是长整型。如：<code>3</code></p>
</li>
<li><p>complax 复数<br>复数，如：<code>3+2.7j</code></p>
</li>
<li><p>list 列表<br>可以包含多种类型的可改变的有序列，如： <code>[1, &quot;a&quot;, b&quot;c&quot;, ]</code></p>
</li>
<li><p>tuple 元组<br>可以包含多种类型的不可改变的有序列，如： <code>(1, &quot;a&quot;, b&quot;c&quot;, )</code></p>
</li>
<li><p>set 集合<br>与数学中集合的概念类似。无序的、每个元素唯一。，如： <code>{1, &quot;a&quot;, b&quot;c&quot;, }</code></p>
</li>
<li><p>dict 字典<br>一个可改变的由键值对组成的无序列。如： <code>{&quot;a&quot;: 1, &quot;b&quot;: 2}</code></p>
</li>
<li><p>bool 布尔值<br>逻辑值。只有两个值：真: <code>True</code>、假: <code>False</code>。</p>
</li>
</ol>
<h3 id="变量与赋值"><a href="#变量与赋值" class="headerlink" title="变量与赋值"></a>变量与赋值</h3><ol>
<li>变量的使用不用预先定义，在使用时赋值即可. 变量只是值的容器，没有类型的限制.</li>
<li>赋值：用等号连接左侧的变量和右侧的值，格式 <code>name = value</code></li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="string">&quot;@@@&quot;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a</span><br><span class="line"><span class="string">&#x27;@@@&#x27;</span></span><br></pre></td></tr></table></figure>

<h3 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h3><p>主要的算术运算符与C&#x2F;C++类似。</p>
<ul>
<li><code>+, -, *, /, //, **, ~, %</code>分别表示加法或者取正、减法或者取负、乘法、除法、整除、乘方、取补、取模。</li>
<li><code>&gt;&gt;, &lt;&lt;</code>表示右移和左移。</li>
<li><code>&amp;, |, ^</code>表示二进制的AND, OR, XOR运算。</li>
<li><code>&gt;, &lt;, ==, !=, &lt;=, &gt;=</code>用于比较两个表达式的值，分别表示大于、小于、等于、不等于、小于等于、大于等于。</li>
<li><code>~, |, ^, &amp;, &lt;&lt;, &gt;&gt;</code>这些运算符, 必须应用于整数。</li>
<li>Python使用<code>and, or, not</code>表示逻辑运算。</li>
<li><code>is, is not</code>用于比较两个变量是否是同一个对象。<code>in, not in</code>用于判断一个对象是否属于另外一个对象。</li>
</ul>
<h3 id="控制流"><a href="#控制流" class="headerlink" title="控制流"></a>控制流</h3><h4 id="循环"><a href="#循环" class="headerlink" title="循环"></a>循环</h4><ol>
<li>for</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(i)</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">2</span></span><br><span class="line"><span class="number">3</span></span><br></pre></td></tr></table></figure>

<ol start="2">
<li>while</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>i = <span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">while</span> i &lt;= <span class="number">3</span>:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(i)</span><br><span class="line"><span class="meta">... </span>    i = i + <span class="number">1</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">2</span></span><br><span class="line"><span class="number">3</span></span><br></pre></td></tr></table></figure>

<ol start="3">
<li>break 结束循环</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(i)</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">if</span> i &gt; <span class="number">1</span>:</span><br><span class="line"><span class="meta">... </span>        <span class="keyword">break</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">2</span></span><br></pre></td></tr></table></figure>

<ol start="4">
<li>continue 跳过本次循环</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]:</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">if</span> i == <span class="number">2</span>:</span><br><span class="line"><span class="meta">... </span>        <span class="keyword">continue</span></span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(i)</span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">3</span></span><br></pre></td></tr></table></figure>

<h4 id="分支"><a href="#分支" class="headerlink" title="分支"></a>分支</h4><p>if&#x2F;else, 其中else不是必须的</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">if</span> a &gt; <span class="number">0</span>:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="number">1</span>)</span><br><span class="line"><span class="meta">... </span><span class="keyword">else</span>:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="number">2</span>)</span><br><span class="line"><span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">if</span> a &gt; <span class="number">0</span>:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="number">1</span>)</span><br><span class="line"><span class="meta">... </span><span class="keyword">elif</span> a == <span class="number">1</span>:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="number">2</span>)</span><br><span class="line"><span class="meta">... </span><span class="keyword">else</span>:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(<span class="number">3</span>)</span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure>

<h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><h4 id="调用函数"><a href="#调用函数" class="headerlink" title="调用函数"></a>调用函数</h4><p>函数的多条语句的集合</p>
<p>Python内置了很多有用的函数，我们可以直接调用。</p>
<p>要调用一个函数，需要知道函数的名称和参数，比如求绝对值的函数abs，只有一个参数。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">abs</span>(-<span class="number">1</span>)</span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure>

<h4 id="定义函数"><a href="#定义函数" class="headerlink" title="定义函数"></a>定义函数</h4><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">def</span> <span class="title function_">my_abs</span>(<span class="params">x</span>):</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">if</span> x &gt;= <span class="number">0</span>:</span><br><span class="line"><span class="meta">... </span>        <span class="keyword">return</span> x</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">else</span>:</span><br><span class="line"><span class="meta">... </span>        <span class="keyword">return</span> -x</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>my_abs(-<span class="number">1</span>)</span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure>

<h3 id="类和实例"><a href="#类和实例" class="headerlink" title="类和实例"></a>类和实例</h3><p>类是面向对象的重要组成元素, 是创造对象的模板, 比如Person类.</p>
<p>而实例是根据类创建出来的一个个具体的“对象”，每个对象都拥有相同的方法，但各自的数据可能不同, 实例是数据和方法的集合.</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">Person</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta">... </span>    total = <span class="number">0</span></span><br><span class="line"><span class="meta">... </span>    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name, age</span>):</span><br><span class="line"><span class="meta">... </span>        <span class="variable language_">self</span>.name = name</span><br><span class="line"><span class="meta">... </span>        <span class="variable language_">self</span>.age = age</span><br><span class="line"><span class="meta">... </span>        Person.total = Person.total + <span class="number">1</span></span><br><span class="line"><span class="meta">... </span>    <span class="keyword">def</span> <span class="title function_">talk</span>(<span class="params">self</span>):</span><br><span class="line"><span class="meta">... </span>        <span class="built_in">print</span>(<span class="string">&#x27;My name is &#123;&#125;, I am &#123;&#125; years old.&#x27;</span>.<span class="built_in">format</span>(<span class="variable language_">self</span>.name, <span class="variable language_">self</span>.age))</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>tom = Person(<span class="string">&#x27;tom&#x27;</span>, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>tom.talk()</span><br><span class="line">My name <span class="keyword">is</span> tom, I am <span class="number">1</span> years old.</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Person.total</span><br><span class="line"><span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>jerry = Person(<span class="string">&#x27;jerry&#x27;</span>, <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>jerry.talk()</span><br><span class="line">My name <span class="keyword">is</span> jerry, I am <span class="number">2</span> years old.</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Person.total</span><br><span class="line"><span class="number">2</span></span><br></pre></td></tr></table></figure>

<h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><h3 id="Web应用开发"><a href="#Web应用开发" class="headerlink" title="Web应用开发"></a>Web应用开发</h3><p>Python经常被用于Web开发, Python定义了WSGI标准应用接口来协调Http服务器与基于Python的Web程序之间的通信。</p>
<p>一些Web框架，可以让程序员轻松地开发和管理复杂的Web程序。</p>
<p>下面介绍最为常见的几个web框架</p>
<h4 id="Django"><a href="#Django" class="headerlink" title="Django"></a>Django</h4><p><a href="https://www.djangoproject.com/">Django</a>的主要目标是使得开发复杂的、数据库驱动的网站变得简单。<br>Django注重组件的重用性和“可插拔性”，敏捷开发和DRY法则（Don’t Repeat Yourself）</p>
<p>大而全的Web框架，适合快速建站，自带的网站管理后台功能（admin）非常强大。</p>
<h4 id="Flask"><a href="#Flask" class="headerlink" title="Flask"></a>Flask</h4><p><a href="http://flask.pocoo.org/">Flask</a>被称为“microframework”，因为它使用简单的核心，同时提供很好的扩展性, 可以用Flask-extension加入这些功能：ORM、窗体验证工具、文件上传、各种开放式身份验证技术。</p>
<p>小而美，适合写api服务，只需10行代码就能写好一个简单服务。Flask的扩展性固然很好，但是如果上面的功能你都需要，那么还是用Django比较好。</p>
<h4 id="Tornado"><a href="#Tornado" class="headerlink" title="Tornado"></a>Tornado</h4><p><a href="http://www.tornadoweb.org/">Tornado</a>全称Tornado Web Server，是一个用Python语言写成的Web服务器兼Web应用框架。</p>
<ul>
<li>作为Web框架，是一个轻量级的Web框架，其拥有异步非阻塞IO的处理方式。</li>
<li>作为Web服务器，Tornado有较为出色的抗负载能力，官方用nginx反向代理的方式部署Tornado和其它Python web应用框架进行对比，结果最大浏览量超过第二名近40%。</li>
</ul>
<p>性能强劲的异步框架，但是如果出问题调起来比较蛋疼。而且有了gevent之后，个人觉得用tornado的性能意义不大。</p>
<h4 id="Sanic"><a href="#Sanic" class="headerlink" title="Sanic"></a>Sanic</h4><p><a href="https://github.com/huge-success/sanic">Sanic</a>是异步框架新秀, 基于原生asyncio构建, 性能强劲, 适合编写高性能服务.</p>
<h3 id="桌面软件"><a href="#桌面软件" class="headerlink" title="桌面软件"></a>桌面软件</h3><h4 id="tkinter"><a href="#tkinter" class="headerlink" title="tkinter"></a>tkinter</h4><p>Tkinter 是 Python 的标准 GUI 库。Tkinter只适合简单界面的程序开发，界面效果是系统原生框体，总之是比较丑。</p>
<p>如果是简单的工具开发，可以用这个。</p>
<h4 id="wxPython"><a href="#wxPython" class="headerlink" title="wxPython"></a>wxPython</h4><p><a href="http://wxpython.org/">wxPython</a>是Python语言的GUI工具包，作为Python的扩展模块实现，包装了wxWidgets。</p>
<p>可配置项比tkinter多些，配起来也比较复杂。</p>
<h4 id="PyQt"><a href="#PyQt" class="headerlink" title="PyQt"></a>PyQt</h4><ul>
<li>PyQt的API与Qt类似，Qt的文档通常仍然可以应用于PyQt。因此，PyQt的文档非常丰富。</li>
<li>如果程序员具备使用Qt的经验，一般很快就可以过渡到PyQt上。</li>
<li>由于PyQt同时使用Qt以及Python的两种内存管理方法，所以在使用PyQt的过程中要注意避免内存泄露以及悬挂指针。需要学习一些C++知识，主要是C++类型、内存管理两个方面，以便于阅读Qt文档和理解PyQt的行为。</li>
</ul>
<p>PyQt应该是最全的跨平台GUI解决方案了，linux上不少程序都是PyQt开发的，而且只要程序颜值也ok，个人推荐。</p>
<h3 id="系统管理"><a href="#系统管理" class="headerlink" title="系统管理"></a>系统管理</h3><p>在很多操作系统里，Python是标准的系统组件。Python标准库包含了多个调用操作系统功能的库。</p>
<p>通过调用这些库, 可以编写很多系统管理的脚本. 例如, 通过pywin32这个第三方软件 包，Python能够访问Windows的COM服务及其它Windows API。</p>
<p>一般说来，Python编写的系统管理脚本在可读性、性能、代码重用度、扩展性几方面都优于普通的shell脚本, 可作为shell脚本的替代。</p>
<p>一般来说100行以上的shell脚本就可以考虑使用Python编写。</p>
<h3 id="云计算"><a href="#云计算" class="headerlink" title="云计算"></a>云计算</h3><p>归功于Python提供覆盖了完善的标准库，以及Python代码的可读性和可维护性，Python在云计算这块也有较广的应用。</p>
<h4 id="OpenStack"><a href="#OpenStack" class="headerlink" title="OpenStack"></a>OpenStack</h4><p>OpenStack是一个美国宇航局和Rackspace合作研发的开源云计算软件</p>
<p>OpenStack是基础设施即服务（IaaS）软件，提供了IaaS整套解决方案，让任何人都可以自行创建和提供云计算服务。</p>
<p>此外，OpenStack也用作创建防火墙内的“私有云”（Private Cloud），提供机构或企业内各部门共享资源。</p>
<h4 id="腾讯云"><a href="#腾讯云" class="headerlink" title="腾讯云"></a>腾讯云</h4><p>据了解，腾讯云大量使用Python做网络以及计算资源的管理。</p>
<h3 id="科学计算"><a href="#科学计算" class="headerlink" title="科学计算"></a>科学计算</h3><h4 id="Matplotlib"><a href="#Matplotlib" class="headerlink" title="Matplotlib"></a>Matplotlib</h4><p>用Python实现的类matlab的第三方库，用以绘制一些高质量的数学二维图形。</p>
<h4 id="NumPy"><a href="#NumPy" class="headerlink" title="NumPy"></a>NumPy</h4><p>基于Python的科学计算第三方库，提供了矩阵，线性代数，傅立叶变换等等的解决方案。</p>
<h4 id="Pandas"><a href="#Pandas" class="headerlink" title="Pandas"></a>Pandas</h4><p>Pandas对numpy进行了封装，是用于数据分析、数据建模、数据可视化的第三方库。</p>
<h4 id="SciPy"><a href="#SciPy" class="headerlink" title="SciPy"></a>SciPy</h4><p>基于Python的matlab实现，旨在实现matlab的所有功能。</p>
<h3 id="游戏"><a href="#游戏" class="headerlink" title="游戏"></a>游戏</h3><p>很多游戏使用C++编写图形显示等高性能模块，而使用Python或者Lua编写游戏的逻辑、服务器。</p>
<h4 id="PyGame"><a href="#PyGame" class="headerlink" title="PyGame"></a>PyGame</h4><p><a href="http://www.pygame.org/">Pygame</a>是跨平台Python模块，专为电子游戏设计。</p>
<p>PyGame适合开发小游戏，大型游戏难度颇大。</p>
<h4 id="cocos2d"><a href="#cocos2d" class="headerlink" title="cocos2d"></a>cocos2d</h4><p><a href="http://cocos2d.org/">cocos2d</a>是一个基于MIT协议的开源框架，最初的Cocos2D框架是使用Python编写的，基于pyglet开发。</p>
<p>可以用于构建游戏、应用程序和其他图形界面交互应用。被大量应用于手机游戏市场，愤怒的小鸟就是基于cocos2d开发。</p>
<h3 id="爬虫"><a href="#爬虫" class="headerlink" title="爬虫"></a>爬虫</h3><h4 id="简单爬虫"><a href="#简单爬虫" class="headerlink" title="简单爬虫"></a>简单爬虫</h4><p>一个简单的爬虫，使用 requests + BeatifulSoup就可以快速实现。</p>
<p>requests请求获取html，通过BeatifulSoup进行解析，就能很轻松拿到想要的数据。</p>
<h4 id="Scrapy"><a href="#Scrapy" class="headerlink" title="Scrapy"></a>Scrapy</h4><p><a href="https://scrapy.org/">Scrapy</a> 是一个爬虫的框架, 而不是一个简单的爬虫.</p>
<p>它整合了爬取, 处理数据, 存储数据的一条龙服务. 能够高效的开发, 爬取网页, 记录数据。</p>
<h3 id="人工智能"><a href="#人工智能" class="headerlink" title="人工智能"></a>人工智能</h3><p>对于整个机器学习，应用层面放眼望去基本就是Python与R的天下了，而R更偏向与统计学领域，而在深度学习Python简直是红透了半边天。</p>
<h4 id="scikit-learn"><a href="#scikit-learn" class="headerlink" title="scikit-learn"></a>scikit-learn</h4><p><a href="http://scikit-learn.org/">Scikit-learn</a>项目最早由数据科学家 David Cournapeau 在 2007 年发起，需要NumPy和SciPy等其他包的支持，是Python语言中专门针对机器学习应用而发展起来的一款开源框架。</p>
<p>Scikit-learn的基本功能主要被分为六大部分：分类，回归，聚类，数据降维，模型选择和数据预处理。</p>
<p>Scikit-learn本身不支持深度学习，也不支持GPU加速，因此这里对于MLP的实现并不适合于处理大规模问题。</p>
<h4 id="TensorFlow"><a href="#TensorFlow" class="headerlink" title="TensorFlow"></a>TensorFlow</h4><p><a href="https://www.tensorflow.org/">TensorFlow</a> 是一个针对深度学习的库，并藉由和谷歌的关系赢得了许多关注。</p>
<ul>
<li>核心库“不仅”适用深度学习，且面向绝大部分的机器学习技术。</li>
<li>除了主要的机器学习功能以外，TensorFlow 还有自己的日志（logging）系统、可交互的日志可视化工具（interactive log visualizer），甚至有工程性非常强的（heavily engineered）服务架构（serving architecture）。</li>
<li>TensorFlow 的执行（execution）模型有别于 Python 的 scikit-learn，或是 R 语言中大部分的工具。</li>
</ul>
<h4 id="Keras"><a href="#Keras" class="headerlink" title="Keras"></a>Keras</h4><p><a href="https://keras.io/">Keras</a>是基于TensorFlow，Theano与CNTK的高阶神经网络API。</p>
<p>keras+tensorflow应该算是比较好的一种解决办法。对于初学者可以用keras搭搭积木，熟悉之后可以和tensorflow配合起来实现很多复杂功能。</p>
<h3 id="局限性"><a href="#局限性" class="headerlink" title="局限性"></a>局限性</h3><p>上面说了这么多Python的应用场景，接下来就说下Python的局限。<br>由于Python本身是解释性语言的，以及GIL的限制，使得其不可避免地由于性能问题被大家诟病。</p>
<p>这里说下个人的几点看法</p>
<ul>
<li>一般情况，开发效率比运行效率，这时Python的运行效率可以接受</li>
<li>适用于IO密集型场景，CPU密集场景不建议适用</li>
<li>动态语言的特性, 对团队技术人员水准有一点要求, 否则代码很可能难以维护.</li>
</ul>
<h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Python3还是Python2"><a href="#Python3还是Python2" class="headerlink" title="Python3还是Python2"></a>Python3还是Python2</h3><p>如果是5年前问这个问题，可能还需要犹豫一下，今天来看毫无疑问是选择3。</p>
<p>到2020年Python2就会完全失去支持，也不会有新的版本出现。还有人专门设立了一个网站进行<a href="https://pythonclock.org/">Python2的死亡倒计时</a>。</p>
<p>Python3还是Python2的问题，其实基本是每一个学习Python的人都会问的问题。</p>
<p>Python3还是Python2的区别主要在unicode的处理，以及一些内置库的命名上，具体可以看<a href="http://ponder.work/2017/08/30/difference-between-python2-and-python3/">这里</a></p>
<h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p><strong>强烈建议学习Python使用类unix系统（如macOS, Ubuntu），虚拟机也可以, 会减少很多疑难问题。</strong></p>
<h4 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h4><p>访问 <a href="https://www.python.org/downloads/"></a> 并下载最新版本的 Python 其安装过程与其它 Windows 平台的软件的安装过程无异。<br>注意：请务必确认你勾选了 Add Python 3.x to PATH 选项。</p>
<h4 id="linux"><a href="#linux" class="headerlink" title="linux"></a>linux</h4><ul>
<li>Ubuntu&#x2F;Debain <code>sudo apt-get update &amp;&amp; sudo apt-get install python3</code></li>
<li>fedora <code>yum update &amp;&amp; yum install python3</code></li>
<li>centos 由于centos不支持Python3，只能自行编译安装</li>
</ul>
<h4 id="macOS"><a href="#macOS" class="headerlink" title="macOS"></a>macOS</h4><p>对于 Mac OS X 用户，你可以使用 <a href="http://brew.sh/">Homebrew</a> 并通过命令 <code>brew install python3</code> 进行安装。</p>
<h4 id="pyenv"><a href="#pyenv" class="headerlink" title="pyenv"></a>pyenv</h4><p>对于macOS，linux，bsd等类unix系统来说，最好的安装管理Python的方式是通过<a href="https://github.com/yyuu/pyenv">pyenv</a>。</p>
<p>pyenv 是 Python 版本管理工具。 pyenv 可以改变全局的 Python 版本，安装多个版本的 Python， 设置目录级别的 Python 版本，还能创建和管理 virtualenv 。所有的设置都是用户级别的操作，不需要 sudo 命令。</p>
<h4 id="conda-推荐"><a href="#conda-推荐" class="headerlink" title="conda(推荐)"></a>conda(推荐)</h4><p>Conda是一个开源跨平台语言无关的包管理与环境管理系统。<br>可以通过创建不同环境来管理Python版本，支持windows，mac，linux。可以解决一些由于系统库缺失或者版本不对导致的问题。</p>
<p>这里推荐使用<a href="https://docs.conda.io/en/latest/miniconda.html">miniconda</a>，体积较小，学习Python完全够用了。<br>如果有机器学习相关需求的，可以选择安装<a href="https://docs.anaconda.com/anaconda/install/">anaconda</a></p>
<h3 id="编辑器-IDE"><a href="#编辑器-IDE" class="headerlink" title="编辑器&#x2F;IDE"></a>编辑器&#x2F;IDE</h3><h4 id="pycharm"><a href="#pycharm" class="headerlink" title="pycharm"></a>pycharm</h4><p><a href="http://www.jetbrains.com/pycharm/">pycharm</a>是jetbrains出品的ide，应该是宇宙最强的吧，有用过他家其他产品的应该很容易上手，有免费的社区版，功能基本够用。</p>
<p>唯一的不足是内存占用有点多，反应偏慢。</p>
<p>功能齐全适合新手使用</p>
<h4 id="Visual-Studio-Code"><a href="#Visual-Studio-Code" class="headerlink" title="Visual Studio Code"></a>Visual Studio Code</h4><p><a href="https://code.visualstudio.com/">vscode</a> 微软出品，免费开源。</p>
<p>安装Python插件之后可作为ide使用，功能也很全，本人主力使用。</p>
<h4 id="Sublime-Text-3"><a href="#Sublime-Text-3" class="headerlink" title="Sublime Text 3"></a>Sublime Text 3</h4><p><a href="https://www.sublimetext.com/">Sublime Text</a>是我第一个使用的代码编辑器，插件很多，响应很快。</p>
<p>vscode、atom一开始都是从Sublime Text上抄功能的。但是随着一段时间的发展Sublime Text渐渐赶不上这两家了，比较它只有一个开发者同时开发3个平台。</p>
<p>通过安装各种插件，基本上都是达到vscode、atom同等水平的易用性，需要多花一些时间配置。</p>
<p>不推荐新手使用</p>
<h3 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h3><h4 id="print大法"><a href="#print大法" class="headerlink" title="print大法"></a>print大法</h4><p>通过打印变量的值进行调试，是最传统也是最直接的调试方法。但是过多的print信息比较难以清理</p>
<h4 id="IDE调试工具（推荐）"><a href="#IDE调试工具（推荐）" class="headerlink" title="IDE调试工具（推荐）"></a>IDE调试工具（推荐）</h4><p>通过PyCharm等现代IDE，可以很方便得进行单步调试，查看当前程序的运行状态，变量的值等。</p>
<p>参考教程 <a href="https://zhuanlan.zhihu.com/p/62610785">https://zhuanlan.zhihu.com/p/62610785</a></p>
<h4 id="logging"><a href="#logging" class="headerlink" title="logging"></a>logging</h4><p>Python自带logging模块，通过简单配置就可以方便地进行日志控制，具体可以参考本人<a href="/2019/11/02/2019-11-02-python-logging-guide/">这篇文章</a></p>
<h4 id="IDLE（交互式命令行）"><a href="#IDLE（交互式命令行）" class="headerlink" title="IDLE（交互式命令行）"></a>IDLE（交互式命令行）</h4><p>启动<code>python</code>命令就可以进入IDLE，通过IDLE，我们可以交互式地查看输入语句的执行结果，适用于新库的使用探索。<br>内置的IDLE功能比较简单，使用起来不是很方便。<br>其实有不少第三方的idle，实现了代码补全、历史记录等实用功能，非常推荐使用。<br>下面是我常用的两个第三方idle</p>
<ul>
<li>ptpython 执行<code>pip install ptpython</code>安装，执行<code>ptpython</code>启动</li>
<li>ipython 执行<code>pip install ipython</code>安装，执行<code>ipython</code>启动</li>
</ul>
<h4 id="pdb"><a href="#pdb" class="headerlink" title="pdb"></a>pdb</h4><p>pdb是Python内置的单步调试库，通过打断点可以方便地对Python代码逐行调试。</p>
<h4 id="gdb"><a href="#gdb" class="headerlink" title="gdb"></a>gdb</h4><p>pdb的使用需要预先在源码中引入，对于已经运行的程序无法调试。<br>这时就可以用到gdb进行调试，通过加载gdb的python插件，可以方便地对运行中的Python程序调试，对程序假死卡住的问题定位很有帮助。</p>
<h3 id="性能优化"><a href="#性能优化" class="headerlink" title="性能优化"></a>性能优化</h3><p>记住一个原则，不要过早优化。<br>一般的应用场景Python的性能都是够用的，如果真的遇到问题，下面也有几个方法解决</p>
<h4 id="C扩展"><a href="#C扩展" class="headerlink" title="C扩展"></a>C扩展</h4><p>通过将程序中计算密集，耗时最多的部分通过编写C扩展的形式脱离出来，在用Python去调用该扩展。该方法可以绕过GIL，能基本彻底解决性能问题，但开发成本基本高。</p>
<h4 id="pypy"><a href="#pypy" class="headerlink" title="pypy"></a>pypy</h4><p><a href="http://pypy.org/">pypy</a>是用Python自身实现的解释器。针对CPython的缺点进行了各方面的改良, 最重要的一点就是Pypy集成了JIT（即时编译器），性能得到很大的提升， 可达CPython的几十倍以上的性能。<br>使用方便，可惜兼容性不佳，有些第三方库不能很好地支持，如numpy。</p>
<h4 id="异步"><a href="#异步" class="headerlink" title="异步"></a>异步</h4><p>如果是IO密集型的性能问题，可通过异步的方式解决问题。</p>
<ul>
<li>特别是对于web服务器，通过gevent的monkey patch，能够解决大部分的并发性能问题，方便快捷无痛，但是C扩展的IO不能被patch。</li>
<li>透过async def、await 编写异步代码，编写成本较高</li>
</ul>
<h3 id="代码规范"><a href="#代码规范" class="headerlink" title="代码规范"></a>代码规范</h3><h4 id="pep8"><a href="#pep8" class="headerlink" title="pep8"></a>pep8</h4><p><a href="https://www.jianshu.com/p/52f4416c267d">pep8</a>是python最普遍接受的代码规范，只要认真阅读一遍 PEP 8，并尽量遵守，你的代码就足够 Pythonic 了。</p>
<h4 id="Google-Python风格规范"><a href="#Google-Python风格规范" class="headerlink" title="Google Python风格规范"></a>Google Python风格规范</h4><p><a href="http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/">Google Python风格规范</a>，对pep8规范进行了扩充，并且有很多实际的例子。个人推荐</p>
<h2 id="学习资源"><a href="#学习资源" class="headerlink" title="学习资源"></a>学习资源</h2><h3 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h3><ul>
<li><a href="https://bop.mol.uno/">简明Python教程</a></li>
<li><a href="https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000">廖雪峰Python教程</a> （推荐）</li>
<li><a href="https://docs.python.org/zh-cn/3/tutorial/index.html">官方Tutorial</a></li>
</ul>
<h3 id="体系化学习"><a href="#体系化学习" class="headerlink" title="体系化学习"></a>体系化学习</h3><ul>
<li><a href="https://book.douban.com/subject/26829016/">Python编程:从入门到实践</a> （推荐）</li>
<li><a href="https://book.douban.com/subject/26675127/">Python语言及其应用</a></li>
<li><a href="https://book.douban.com/subject/5401851/">Python参考手册</a></li>
<li><a href="https://book.douban.com/subject/4828875/">Python Cookbook</a></li>
</ul>
<h3 id="深入学习"><a href="#深入学习" class="headerlink" title="深入学习"></a>深入学习</h3><ul>
<li><a href="https://book.douban.com/subject/27028517/">流畅的Python</a></li>
<li><a href="https://book.douban.com/subject/3117898/">Python源码剖析</a></li>
<li><a href="https://book.douban.com/subject/27064848/">Python高性能编程</a></li>
</ul>
<h3 id="特定领域学习"><a href="#特定领域学习" class="headerlink" title="特定领域学习"></a>特定领域学习</h3><ul>
<li><a href="https://book.douban.com/subject/26274202/">Flask Web开发</a></li>
<li><a href="https://book.douban.com/subject/25779298/">利用Python进行数据分析</a></li>
<li><a href="https://book.douban.com/subject/26640135/">Python Web开发：测试驱动方法</a></li>
</ul>
<h3 id="微软Python教程视频"><a href="#微软Python教程视频" class="headerlink" title="微软Python教程视频"></a>微软Python教程视频</h3><ul>
<li>炼气： <a href="https://www.bilibili.com/video/BV1nE41127zQ">https://www.bilibili.com/video/BV1nE41127zQ</a></li>
<li>筑基： <a href="https://www.bilibili.com/video/BV1WT4y137cD">https://www.bilibili.com/video/BV1WT4y137cD</a></li>
<li>金丹： <a href="https://www.bilibili.com/video/BV1qa4y1Y7CD">https://www.bilibili.com/video/BV1qa4y1Y7CD</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python日志原理及实践</title>
    <url>/2019/11/02/python-logging-guide/</url>
    <content><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><h3 id="一次简单的日志记录"><a href="#一次简单的日志记录" class="headerlink" title="一次简单的日志记录"></a>一次简单的日志记录</h3><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">import<span class="built_in"> logging</span></span><br><span class="line"><span class="built_in"></span>logger = logging.getLogger(__name__)</span><br><span class="line"></span><br><span class="line">try:</span><br><span class="line">    1/0</span><br><span class="line">except Exception as e:</span><br><span class="line">    logger.<span class="built_in">debug</span>(<span class="string">&#x27;debug %s&#x27;</span>, e)</span><br><span class="line">    logger.<span class="built_in">info</span>(<span class="string">&#x27;info %s&#x27;</span>, e)</span><br><span class="line">    logger.<span class="built_in">warning</span>(<span class="string">&#x27;warning %s&#x27;</span>, e)</span><br><span class="line">    logger.<span class="built_in">error</span>(<span class="string">&#x27;error %s&#x27;</span>, e)</span><br><span class="line">    logger.exception(<span class="string">&#x27;exception %s&#x27;</span>, e)</span><br></pre></td></tr></table></figure>

<p>output</p>
<figure class="highlight livecodeserver"><table><tr><td class="code"><pre><span class="line">warning division <span class="keyword">by</span> <span class="literal">zero</span></span><br><span class="line">error division <span class="keyword">by</span> <span class="literal">zero</span></span><br><span class="line">exception division <span class="keyword">by</span> <span class="literal">zero</span></span><br><span class="line">Traceback (most recent call <span class="keyword">last</span>):</span><br><span class="line">  File <span class="string">&quot;test.py&quot;</span>, <span class="built_in">line</span> <span class="number">5</span>, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    <span class="number">1</span>/<span class="number">0</span></span><br><span class="line">ZeroDivisionError: division <span class="keyword">by</span> <span class="literal">zero</span></span><br></pre></td></tr></table></figure>

<ol>
<li><code>logging.getLogger</code> 获取一个记录器, 一般以模块名称命名</li>
<li>调用日志记录器的相应方法记录日志</li>
</ol>
<span id="more"></span>

<h3 id="记录器-logger"><a href="#记录器-logger" class="headerlink" title="记录器 (logger)"></a>记录器 (logger)</h3><p><img data-src="https://image.ponder.work/mweb/2019-11-02-15441717816714.jpg"></p>
<ul>
<li>logger是Python记录日志的入口</li>
<li>logger.info, logger.debug等几个函数对应记录不同的日志级别</li>
<li>日志配置可以设置日志级别, 低于日志级别的日志不被记录</li>
<li>默认日志配置的级别是WARNING</li>
</ul>
<p>Python日志支持的级别</p>
<table>
<thead>
<tr>
<th>级别</th>
<th>何时使用</th>
</tr>
</thead>
<tbody><tr>
<td>DEBUG</td>
<td>详细信息，典型地调试问题时会感兴趣。</td>
</tr>
<tr>
<td>INFO</td>
<td>证明事情按预期工作。</td>
</tr>
<tr>
<td>WARNING</td>
<td>表明发生了一些意外，或者不久的将来会发生问题软件还是在正常工作。</td>
</tr>
<tr>
<td>ERROR</td>
<td>由于更严重的问题，软件已不能执行一些功能了。</td>
</tr>
<tr>
<td>CRITICAL</td>
<td>严重错误，表明软件已不能继续运行了。</td>
</tr>
</tbody></table>
<h3 id="记录-record"><a href="#记录-record" class="headerlink" title="记录 (record)"></a>记录 (record)</h3><p>每个record对象代表一条日志<br>record拥很多有的属性， 可扩展, 主要被formatter使用</p>
<p>日志记录器的记录方法每次被调用就会生成一条record对象, 并且将record的所有属性都设置好.</p>
<p>record主要有以下属性</p>
<table>
<thead>
<tr>
<th>name</th>
<th>logger名称</th>
</tr>
</thead>
<tbody><tr>
<td>msg</td>
<td>日志文本</td>
</tr>
<tr>
<td>args</td>
<td>格式文本的参数</td>
</tr>
<tr>
<td>levelname</td>
<td>级别名称</td>
</tr>
<tr>
<td>module</td>
<td>模块名称</td>
</tr>
<tr>
<td>exc_info</td>
<td>异常相关信息</td>
</tr>
<tr>
<td>lineno</td>
<td>行号</td>
</tr>
<tr>
<td>funcName</td>
<td>打日志的函数名称</td>
</tr>
<tr>
<td>threadName</td>
<td>线程名</td>
</tr>
<tr>
<td>processName</td>
<td>进程名称</td>
</tr>
</tbody></table>
<h3 id="处理器-handler"><a href="#处理器-handler" class="headerlink" title="处理器 (handler)"></a>处理器 (handler)</h3><p>日志处理器决定了日志的存储的方式， 存储大小，存储的位置<br>日志处理器通过handle方法处理日志, 不同的处理器行为差别很大</p>
<p>一些常见的日志处理器</p>
<ul>
<li>StreamHandler 输出到终端</li>
<li>SMTPHandler 通过 email 发送日志记录</li>
<li>SocketHandler 将日志通过网络发送</li>
<li>FileHandler  记录到文件</li>
<li>TimedRotatingFileHandler 记录到文件并按时间滚动</li>
</ul>
<h3 id="过滤器-filter"><a href="#过滤器-filter" class="headerlink" title="过滤器 (filter)"></a>过滤器 (filter)</h3><p>filter是附加在logger上的, 对日志进行细粒度控制, 例如根据一些条件判断是否记录日志, 修改日志record的内容等</p>
<p><img data-src="https://image.ponder.work/mweb/2019-11-02-15441884511996.jpg"></p>
<p>例如上面这个ContextFilter, 就将第二条日志过滤掉了.</p>
<h3 id="格式器-formatter"><a href="#格式器-formatter" class="headerlink" title="格式器 (formatter)"></a>格式器 (formatter)</h3><p>根据配置，将日志record的属性格式化成字符串<br><img data-src="https://image.ponder.work/mweb/2019-11-02-15441892815553.jpg"></p>
<p><img data-src="https://image.ponder.work/mweb/2019-11-02-15441892888667.jpg"></p>
<h3 id="日志处理流程"><a href="#日志处理流程" class="headerlink" title="日志处理流程"></a>日志处理流程</h3><p>解释完这些名词, 然后看这个这个流程图, 就会明白整个日志的处理流程</p>
<p><img data-src="https://image.ponder.work/mweb/2019-11-02-15393262305519.jpg"></p>
<h3 id="日志配置"><a href="#日志配置" class="headerlink" title="日志配置"></a>日志配置</h3><p>开发过程中觉得比较好的一个日志配置</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">LOGGING = &#123;</span><br><span class="line">    <span class="string">&#x27;version&#x27;</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="string">&#x27;disable_existing_loggers&#x27;</span>: <span class="literal">False</span>,  <span class="comment"># 是否禁用已存在的日志记录器</span></span><br><span class="line">    <span class="string">&#x27;formatters&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;verbose&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;format&#x27;</span>: <span class="string">&#x27;[%(levelname)s %(asctime)s %(name)s %(funcName)s %(lineno)d %(process)d] %(message)s&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">&#x27;simple&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;format&#x27;</span>: <span class="string">&#x27;[%(levelname)s %(message)s&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&#x27;handlers&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;console&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;INFO&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;class&#x27;</span>: <span class="string">&#x27;logging.StreamHandler&#x27;</span>,   <span class="comment"># 输出到终端的handlers</span></span><br><span class="line">            <span class="string">&#x27;formatter&#x27;</span>: <span class="string">&#x27;simple&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">&#x27;backend_file&#x27;</span>:&#123;</span><br><span class="line">            <span class="string">&#x27;class&#x27;</span>: <span class="string">&#x27;logging.handlers.TimedRotatingFileHandler&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;filename&#x27;</span>: <span class="string">&#x27;main.log&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;backupCount&#x27;</span>: <span class="number">5</span>,</span><br><span class="line">            <span class="string">&#x27;formatter&#x27;</span>:<span class="string">&#x27;verbose&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;when&#x27;</span>: <span class="string">&#x27;D&#x27;</span>,  <span class="comment"># 按天滚动日志</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">&#x27;backend_err_file&#x27;</span>:&#123;</span><br><span class="line">            <span class="string">&#x27;class&#x27;</span>: <span class="string">&#x27;logging.handlers.TimedRotatingFileHandler&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;filename&#x27;</span>: <span class="string">&#x27;main_error.log&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;backupCount&#x27;</span>: <span class="number">5</span>,</span><br><span class="line">            <span class="string">&#x27;formatter&#x27;</span>:<span class="string">&#x27;verbose&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;ERROR&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;when&#x27;</span>: <span class="string">&#x27;D&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&#x27;loggers&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;handlers&#x27;</span>: [<span class="string">&#x27;backend_file&#x27;</span>, <span class="string">&#x27;backend_err_file&#x27;</span>],</span><br><span class="line">            <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;INFO&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;propagate&#x27;</span>: <span class="literal">False</span>,  <span class="comment"># 是否传递给上级logger，一般设置为False；否则，可能会导致多份日志</span></span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> logging.config</span><br><span class="line"></span><br><span class="line">logging.config.dictConfig(LOGGING)</span><br></pre></td></tr></table></figure>

<h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><h3 id="要打哪些日志"><a href="#要打哪些日志" class="headerlink" title="要打哪些日志"></a>要打哪些日志</h3><h4 id="问题定位"><a href="#问题定位" class="headerlink" title="问题定位"></a>问题定位</h4><ul>
<li>web请求入口和出口</li>
<li>外部服务调用入参和返回</li>
<li>未预料的程序异常</li>
<li>关键流程记录</li>
<li>启动、关闭、配置加载</li>
</ul>
<h4 id="性能分析"><a href="#性能分析" class="headerlink" title="性能分析"></a>性能分析</h4><ul>
<li>函数\服务调用耗时记录</li>
</ul>
<h3 id="每条日志要包含哪些内容"><a href="#每条日志要包含哪些内容" class="headerlink" title="每条日志要包含哪些内容"></a>每条日志要包含哪些内容</h3><ul>
<li><p>基本信息</p>
  <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="string">&#x27;%(levelname)s %(asctime)s %(name)s %(funcName)s %(lineno)d %(process)d %(thread)d %(message)s&#x27;</span></span><br><span class="line"></span><br><span class="line">日志级别 时间 logger名称 函数名称 函数名 行号 进程<span class="built_in">id</span> 线程<span class="built_in">id</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>唯一请求id</p>
</li>
<li><p>必要的描述信息: 如request xxx failed</p>
</li>
<li><p>异常相关信息, 调用trace info</p>
</li>
</ul>
<h3 id="日志级别"><a href="#日志级别" class="headerlink" title="日志级别"></a>日志级别</h3><p>正确地使用日志级别, 不要乱用级别<br><strong>ERROR</strong>：该级别的错误也需要马上被处理。当ERROR错误发生时，已经影响了用户的正常访问，是需要马上得到人工介入并处理的。<br><strong>WARNING</strong>：该日志表示系统可能出现问题，也可能没有，这种情况如网络的波动等。对于WARN级别的日志，虽然不需要系统管理员马上处理，也是需要及时查看并处理的。因此此种级别的日志也不应太多，能不打WARNING级别的日志，就尽量不要打；<br><strong>INFO</strong>：该种日志记录系统的正常运行状态, 占日志的大部分；<br><strong>DEBUG</strong>：开发过程中的调试信息。</p>
<h3 id="日志存储"><a href="#日志存储" class="headerlink" title="日志存储"></a>日志存储</h3><h4 id="文件"><a href="#文件" class="headerlink" title="文件"></a>文件</h4><ul>
<li>日志滚动: 按时间或按大小滚动. 同时配置最大保存的数目</li>
<li>存储路径: 放到空间比较大目录</li>
<li>注意是否多进程写日志</li>
<li>error级别日志输出到单独文件</li>
</ul>
<h4 id="网络日志"><a href="#网络日志" class="headerlink" title="网络日志"></a>网络日志</h4><ul>
<li>单机情况下一般不需要</li>
<li>多机部署可用kafka，ELK等方案</li>
</ul>
<h3 id="一些记录的建议"><a href="#一些记录的建议" class="headerlink" title="一些记录的建议"></a>一些记录的建议</h3><ul>
<li>不要使用print代替日志</li>
<li>日志对程序性能有影响, 不要打很多没有必要的日志.</li>
<li>不同的日志handler有不同的适用场景, 要选择合适的handler</li>
<li>Python内置的所有handler只是线程安全, 多进程场景可以打日志到多个文件, 或者使用网络日志</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>日志</tag>
      </tags>
  </entry>
  <entry>
    <title>Python 循环变量泄露与延迟绑定</title>
    <url>/2022/03/04/python-loop-variables-leak/</url>
    <content><![CDATA[<p>循环变量泄露与延迟绑定叠加在一起，会产生一些让人迷惑的结果。</p>
<span id="more"></span>

<h2 id="梦开始的地方"><a href="#梦开始的地方" class="headerlink" title="梦开始的地方"></a>梦开始的地方</h2><p>先看看一开始的问题，可以看到这里lambda函数的返回值一直在变。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">xx = []</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]:</span><br><span class="line">    xx.append(<span class="keyword">lambda</span>: i)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;a:&#x27;</span>, xx[<span class="number">0</span>]())</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> j <span class="keyword">in</span> xx:</span><br><span class="line">    <span class="built_in">print</span>(j())</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;b:&#x27;</span>, xx[<span class="number">0</span>]())</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> xx:</span><br><span class="line">    <span class="built_in">print</span>(i, i())</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;c:&#x27;</span>, xx[<span class="number">0</span>], xx[<span class="number">0</span>]())</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>]:</span><br><span class="line">    <span class="built_in">print</span>(i)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;d:&#x27;</span>, xx[<span class="number">0</span>], xx[<span class="number">0</span>]())</span><br></pre></td></tr></table></figure>

<p>输出如下</p>
<figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">a:</span> <span class="number">3</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="params">b:</span> <span class="number">3</span></span><br><span class="line"><span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30310<span class="operator">&gt;</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30310<span class="operator">&gt;</span></span><br><span class="line"><span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca303a0<span class="operator">&gt;</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca303a0<span class="operator">&gt;</span></span><br><span class="line"><span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30430<span class="operator">&gt;</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30430<span class="operator">&gt;</span></span><br><span class="line"><span class="params">c:</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30310<span class="operator">&gt;</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30430<span class="operator">&gt;</span></span><br><span class="line"><span class="number">4</span></span><br><span class="line"><span class="number">5</span></span><br><span class="line"><span class="number">6</span></span><br><span class="line"><span class="params">d:</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30310<span class="operator">&gt;</span> <span class="number">6</span></span><br></pre></td></tr></table></figure>

<h2 id="循环变量泄露"><a href="#循环变量泄露" class="headerlink" title="循环变量泄露"></a>循环变量泄露</h2><p>由于Python没有块级作用域，所以循环会改变当前作用域变量的值，也就是循环变量泄露。<br><strong>注意</strong>：Python3中列表推导式循环变量不会泄露，Python2中和常规循环一样泄露。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">x = -<span class="number">1</span></span><br><span class="line"><span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">7</span>):</span><br><span class="line">    <span class="keyword">if</span> x == <span class="number">6</span>:</span><br><span class="line">        <span class="built_in">print</span>(x, <span class="string">&#x27;: for x inside loop&#x27;</span>)</span><br><span class="line"><span class="built_in">print</span>(x, <span class="string">&#x27;: x in global&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>输出如下</p>
<figure class="highlight basic"><table><tr><td class="code"><pre><span class="line"><span class="symbol">6 </span>: <span class="keyword">for</span> x inside loop</span><br><span class="line"><span class="symbol">6 </span>: x in global</span><br></pre></td></tr></table></figure>

<h2 id="闭包与延迟绑定"><a href="#闭包与延迟绑定" class="headerlink" title="闭包与延迟绑定"></a>闭包与延迟绑定</h2><p>再讲一下<strong>闭包</strong>，在一个内部函数中，对外部作用域的变量进行引用，(并且一般外部函数的返回值为内部函数)，那么内部函数就被认为是闭包。<br>这里所谓的引用可以也就是内部函数记住了<strong>变量的名称</strong>（而不是值，这个从ast语法树可以看出），而变量对应的值是会变化的。<br>如果在循环中定义闭包，引用的变量的值在循环结束才统一确定为最后一次循环时的值，也就是<strong>延迟绑定</strong>（lazy binding）。</p>
<p>所以下面的例子，<code>xx</code>的所有匿名函数的返回值均为<code>3</code></p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">xx = <span class="selector-attr">[]</span></span><br><span class="line"><span class="keyword">for</span> <span class="selector-tag">i</span> <span class="keyword">in</span> <span class="selector-attr">[1,2,3]</span>:</span><br><span class="line">    xx<span class="selector-class">.append</span>(lambda: i)</span><br></pre></td></tr></table></figure>

<h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>再分析一开始的问题，这里的匿名函数引用了变量<code>i</code>，而<code>i</code>是全局变量，所以再次使用<code>i</code>作为循环变量时，列表中的匿名函数引用的值就被覆盖了。</p>
<p>正确做法：</p>
<ul>
<li>在独立的函数中定义闭包</li>
<li>闭包引用的变量应该是其他函数不可修改的</li>
<li>优先使用列表推导式</li>
</ul>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://stackoverflow.com/questions/3611760/scoping-in-python-for-loops">https://stackoverflow.com/questions/3611760/scoping-in-python-for-loops</a></li>
<li><a href="https://www.educative.io/courses/python-ftw-under-the-hood/N8RW8508RkL">https://www.educative.io/courses/python-ftw-under-the-hood/N8RW8508RkL</a></li>
<li><a href="https://mail.python.org/pipermail/python-ideas/2008-October/002109.html">https://mail.python.org/pipermail/python-ideas/2008-October/002109.html</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python排序算法总结</title>
    <url>/2016/01/20/python-sort-algorithms/</url>
    <content><![CDATA[<h2 id="冒泡排序-BubbleSort"><a href="#冒泡排序-BubbleSort" class="headerlink" title="冒泡排序 BubbleSort"></a>冒泡排序 BubbleSort</h2><h3 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h3><p>遍历序列，比较两个元素，如果前面的大于后面的就交换两者的位置。其实称之为冒泡排序不如加沉底排序，因为每一轮比较，这一轮轮最大都会被排到序列末尾，其实沉底更为贴切。</p>
<h3 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h3><ol>
<li>遍历序列，比较序列的相邻元素，比较n-1次，如果前面的大于后面的就交换两者的位置。</li>
<li>比较次数减一，重复步骤1</li>
<li>共遍历n-1次</li>
</ol>
<h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><ol>
<li>原始版本</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">bubble_sort</span>(<span class="params">arry</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(arry)</span><br><span class="line">    <span class="keyword">while</span> n &gt; <span class="number">1</span>:</span><br><span class="line">        n -= <span class="number">1</span></span><br><span class="line">        <span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">            <span class="keyword">if</span> arry[x] &gt; arry[x+<span class="number">1</span>]:</span><br><span class="line">                arry[x], arry[x+<span class="number">1</span>] = arry[x+<span class="number">1</span>], arry[x]</span><br><span class="line">    <span class="keyword">return</span> arry</span><br></pre></td></tr></table></figure>
<ol start="2">
<li>改进版本</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">bubble_sort</span>(<span class="params">arry</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(arry)</span><br><span class="line">    <span class="keyword">while</span> n &gt; <span class="number">1</span>:</span><br><span class="line">        n -= <span class="number">1</span></span><br><span class="line">        swap_flag = <span class="literal">False</span>  <span class="comment"># 增加一个标记，当排好序后直接退出</span></span><br><span class="line">        <span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">            <span class="keyword">if</span> arry[x] &gt; arry[x+<span class="number">1</span>]:</span><br><span class="line">                arry[x], arry[x+<span class="number">1</span>] = arry[x+<span class="number">1</span>], arry[x]</span><br><span class="line">                swap_flag = <span class="literal">True</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> swap_flag:</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">return</span> arry</span><br></pre></td></tr></table></figure>

<span id="more"></span>

<h2 id="选择排序-SelectionSort"><a href="#选择排序-SelectionSort" class="headerlink" title="选择排序 SelectionSort"></a>选择排序 SelectionSort</h2><h3 id="介绍-1"><a href="#介绍-1" class="headerlink" title="介绍"></a>介绍</h3><p>还是先来看看选择排序的思想。选择排序的思想非常直接，不是要排序么？那好，我就从所有序列中先找到最小的，然后放到第一个位置。之后再看剩余元素中最小的，放到第二个位置……以此类推，就可以完成整个的排序工作了。</p>
<h3 id="步骤-1"><a href="#步骤-1" class="headerlink" title="步骤"></a>步骤</h3><ol>
<li>在未排序序列中找到最小元素，存放到序列的起始位置。</li>
<li>在未排序序列中找到最小元素，存放到序列的第二位置。</li>
<li>以此类推，共遍历序列n次</li>
</ol>
<h3 id="代码-1"><a href="#代码-1" class="headerlink" title="代码"></a>代码</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">selection_sort</span>(<span class="params">arry</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(arry)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, n):</span><br><span class="line">        <span class="built_in">min</span> = i</span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(i+<span class="number">1</span>, n):</span><br><span class="line">            <span class="keyword">if</span> arry[j] &lt; arry[<span class="built_in">min</span>]:</span><br><span class="line">                <span class="built_in">min</span> = j</span><br><span class="line">        arry[i], arry[<span class="built_in">min</span>] = arry[<span class="built_in">min</span>], arry[i]</span><br><span class="line">    <span class="keyword">return</span> arry</span><br></pre></td></tr></table></figure>

<h2 id="插入排序-InsertionSort"><a href="#插入排序-InsertionSort" class="headerlink" title="插入排序 InsertionSort"></a>插入排序 InsertionSort</h2><h3 id="介绍-2"><a href="#介绍-2" class="headerlink" title="介绍"></a>介绍</h3><p>插入排序（Insertion Sort）是一种简单直观的排序算法。它的工作原理是通过构建有序序列，对于未排序数据，在已排序序列中从后向前扫描，找到相应位置并插入。</p>
<h3 id="步骤-2"><a href="#步骤-2" class="headerlink" title="步骤"></a>步骤</h3><ol>
<li>从第一个元素开始，该元素可以认为已经被排序</li>
<li>取出下一个元素，在已经排序的元素序列中从后向前扫描</li>
<li>如果该元素（已排序）大于新元素，将该元素移到下一位置</li>
<li>重复步骤3，直到找到已排序的元素小于或者等于新元素的位置</li>
<li>将新元素插入到该位置后</li>
<li>重复步骤2~5</li>
</ol>
<h3 id="代码-2"><a href="#代码-2" class="headerlink" title="代码"></a>代码</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">Insertion_sort</span>(<span class="params">arry</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(arry)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, n):</span><br><span class="line">        temp = arry[i]</span><br><span class="line">        index = i</span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(i-<span class="number">1</span>, -<span class="number">1</span>, -<span class="number">1</span>):</span><br><span class="line">            <span class="keyword">if</span> temp &lt; arry[j]:</span><br><span class="line">                arry[j+<span class="number">1</span>] = arry[j]</span><br><span class="line">                index = j</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">        arry[index] = temp</span><br><span class="line">    <span class="keyword">return</span> arry</span><br></pre></td></tr></table></figure>
<h2 id="希尔排序-ShellSort"><a href="#希尔排序-ShellSort" class="headerlink" title="希尔排序 ShellSort"></a>希尔排序 ShellSort</h2><h3 id="介绍-3"><a href="#介绍-3" class="headerlink" title="介绍"></a>介绍</h3><p>希尔排序，也称递减增量排序算法，是插入排序的一种更高效的改进版本，我称之为<code>分组插入排序</code>。希尔排序是非稳定排序算法。<br>希尔排序是基于插入排序的以下两点性质而提出改进方法的</p>
<ul>
<li>插入排序在对几乎已经排好序的数据操作时，效率高，即可以达到线性排序的效率</li>
<li>但插入排序一般来说是低效的，因为插入排序每次只能将数据移动一位</li>
</ul>
<h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><p>希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序，算法的最后一步就是普通的插入排序，但是到了这步，需排序的数据几乎是已排好的了（此时插入排序较快）。</p>
<p>假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n2)的排序（冒泡排序或插入排序），可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据，所以小数据只需进行少数比较和交换即可到正确位置。</p>
<h3 id="代码-3"><a href="#代码-3" class="headerlink" title="代码"></a>代码</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">shell_sort</span>(<span class="params">arry</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(arry)</span><br><span class="line">    step = <span class="built_in">int</span>(<span class="built_in">round</span>(n/<span class="number">2.0</span>))</span><br><span class="line">    <span class="keyword">while</span> step &gt; <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(step, n):</span><br><span class="line">            temp = arry[i]</span><br><span class="line">            index = i </span><br><span class="line">            <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(i-step, -<span class="number">1</span>, -step):</span><br><span class="line">                <span class="keyword">if</span> temp &lt; arry[j]:</span><br><span class="line">                    arry[j+step] = arry[j]</span><br><span class="line">                    index = j</span><br><span class="line">                <span class="keyword">else</span>:</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line">            arry[index] = temp</span><br><span class="line">        step = <span class="built_in">int</span>(<span class="built_in">round</span>(step/<span class="number">2</span>))</span><br><span class="line">    <span class="keyword">return</span> arry</span><br></pre></td></tr></table></figure>
<h2 id="归并排序-MergeSort"><a href="#归并排序-MergeSort" class="headerlink" title="归并排序 MergeSort"></a>归并排序 MergeSort</h2><h3 id="介绍-4"><a href="#介绍-4" class="headerlink" title="介绍"></a>介绍</h3><p>归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法（Divide and Conquer）的一个非常典型的应用。</p>
<p>首先考虑下如何将将二个有序数列合并。这个非常简单，只要从比较二个数列的第一个数，谁小就先取谁，取了后就在对应数列中删除这个数。然后再进行比较，如果有数列为空，那直接将另一个数列的数据依次取出即可。</p>
<h3 id="实现-1"><a href="#实现-1" class="headerlink" title="实现"></a>实现</h3><ol>
<li>先将序列递归分组，直到每组只有一个元素。</li>
<li>将序列两两合并：只要从比较二个数列的第一个数，谁小就先取谁，取了后就在对应数列中删除这个数。然后再进行比较，如果有数列为空，那直接将另一个数列的数据依次取出即可。</li>
<li>重复步骤2，直到所有序列都合并。</li>
</ol>
<h3 id="代码-4"><a href="#代码-4" class="headerlink" title="代码"></a>代码</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">merge_sort</span>(<span class="params">arry</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(arry) &lt;= <span class="number">1</span>:</span><br><span class="line">        <span class="keyword">return</span> arry</span><br><span class="line">    num = <span class="built_in">int</span>(<span class="built_in">len</span>(arry)/<span class="number">2</span>)</span><br><span class="line">    left = merge_sort(arry[:num])</span><br><span class="line">    right = merge_sort(arry[num:])</span><br><span class="line">    <span class="keyword">return</span> merge(left, right)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">merge</span>(<span class="params">left, right</span>):</span><br><span class="line">    l, r = <span class="number">0</span>, <span class="number">0</span>  <span class="comment">#左右两个序列的指针</span></span><br><span class="line">    result = []</span><br><span class="line">    <span class="keyword">while</span> l&lt;<span class="built_in">len</span>(left) <span class="keyword">and</span> r&lt;<span class="built_in">len</span>(right):</span><br><span class="line">        <span class="keyword">if</span> left[l] &lt; right[r]:</span><br><span class="line">            result.append(left[l])</span><br><span class="line">            l += <span class="number">1</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            result.append(right[r])</span><br><span class="line">            r += <span class="number">1</span></span><br><span class="line">    result += left[l:]</span><br><span class="line">    result += right[r:]</span><br><span class="line">    <span class="keyword">return</span> result</span><br></pre></td></tr></table></figure>

<h2 id="快速排序-QuickSort"><a href="#快速排序-QuickSort" class="headerlink" title="快速排序 QuickSort"></a>快速排序 QuickSort</h2><h3 id="介绍-5"><a href="#介绍-5" class="headerlink" title="介绍"></a>介绍</h3><p>快速排序使用分治法（Divide and conquer）策略来把一个序列（list）分为两个子序列（sub-lists）。一个序列大于基准，一个小于基准。再对这两个序列进行同样的操作，以此类推直到所有元素都排列好。</p>
<h3 id="步骤-3"><a href="#步骤-3" class="headerlink" title="步骤"></a>步骤</h3><ol>
<li>从数列中挑出一个元素，称为”基准”（pivot），</li>
<li>重新排序数列，所有元素比基准值小的摆放在基准前面，所有元素比基准值大的摆在基准的后面（相同的数可以到任一边）。在这个分割结束之后，该基准就处于数列的中间位置。这个称为分割（partition）操作。</li>
<li>递归地（recursive）把小于基淮值元素的子数列和大于基淮值元素的子数列排序。递归的最底部情形，是数列的大小是零或一，也就是永远都已经被排序好了。虽然一直递迴下去，但是这个演算法总会结束，因为在每次的迭代（iteration）中，它至少会把一个元素摆到它最后的位置去。</li>
</ol>
<h3 id="代码-5"><a href="#代码-5" class="headerlink" title="代码"></a>代码</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 该实现是原位排序, 所以和上面的步骤有点区别</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">quick_sort</span>(<span class="params">arry</span>):</span><br><span class="line">    <span class="keyword">return</span> qsort(arry, <span class="number">0</span>, <span class="built_in">len</span>(arry)-<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">qsort</span>(<span class="params">arry, left, right</span>):</span><br><span class="line">    <span class="keyword">if</span> left &gt;= right:</span><br><span class="line">        <span class="keyword">return</span> arry</span><br><span class="line">    key = arry[right]  <span class="comment"># 以未排序序列最后一个元素为基准</span></span><br><span class="line">    l = left</span><br><span class="line">    r = right</span><br><span class="line">    <span class="keyword">while</span> l &lt; r:</span><br><span class="line">        <span class="keyword">if</span> arry[l] &lt;= key:</span><br><span class="line">            l += <span class="number">1</span></span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="keyword">if</span> arry[r] &gt;= key:</span><br><span class="line">            r -= <span class="number">1</span></span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        arry[l], arry[r] = arry[r], arry[l]  <span class="comment"># 交换 左边比基准大, 右边比基准小的两个元素</span></span><br><span class="line">    <span class="comment"># 循环完成时l == r, 此时 arry[r-1] &lt; arry[right] &lt; arry[r]</span></span><br><span class="line">    <span class="comment"># 最后交换 arry[right] 和 arry[r], 则left 到 r-1 都是小于arry[r]; r+1到right 都是大于arry[r]</span></span><br><span class="line">    arry[r], arry[right] = arry[right], arry[r]</span><br><span class="line">    qsort(arry, left, r-<span class="number">1</span>)</span><br><span class="line">    qsort(arry, r+<span class="number">1</span>, right)</span><br><span class="line">    <span class="keyword">return</span> arry</span><br></pre></td></tr></table></figure>

<h2 id="桶排序-BucketSort"><a href="#桶排序-BucketSort" class="headerlink" title="桶排序 BucketSort"></a>桶排序 BucketSort</h2><h3 id="介绍-6"><a href="#介绍-6" class="headerlink" title="介绍"></a>介绍</h3><p>桶排序（Bucket sort）或所谓的箱排序，是一个排序算法，工作的原理是将数组分到有限数量的桶里。每个桶再个别排序（有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序）。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候，桶排序使用线性时间（Θ(n)）。但桶排序并不是比较排序，他不受到O(n log n)下限的影响。</p>
<h3 id="步骤-4"><a href="#步骤-4" class="headerlink" title="步骤"></a>步骤</h3><ol>
<li>根据数据的分布范围建桶，比如年龄，就可以建1-100号的桶。</li>
<li>根据每个数据的值，将它放到对应的桶中</li>
<li>从1号到100号桶中，依次将数据倒出</li>
</ol>
<h3 id="代码-6"><a href="#代码-6" class="headerlink" title="代码"></a>代码</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">bucket_sort</span>(<span class="params">array</span>):</span><br><span class="line">    buckets = &#123;i:<span class="number">0</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">101</span>)&#125;  <span class="comment"># 100 bucket</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> array:</span><br><span class="line">        buckets[i] += <span class="number">1</span></span><br><span class="line">    result = []</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> xrange(<span class="number">1</span>,<span class="number">101</span>):</span><br><span class="line">        result.extend([i] * buckets[i])</span><br><span class="line">    <span class="keyword">return</span> result</span><br></pre></td></tr></table></figure>

<h2 id="堆排序"><a href="#堆排序" class="headerlink" title="堆排序"></a>堆排序</h2><h3 id="介绍-7"><a href="#介绍-7" class="headerlink" title="介绍"></a>介绍</h3><p>堆排序在 top K 问题中使用比较频繁。堆排序是采用二叉堆的数据结构来实现的，虽然实质上还是一维数组。二叉堆是一个近似完全二叉树 。</p>
<p>二叉堆具有以下性质：</p>
<p>父节点的键值总是大于或等于（小于或等于）任何一个子节点的键值。<br>每个节点的左右子树都是一个二叉堆（都是最大堆或最小堆）。</p>
<h3 id="步骤-5"><a href="#步骤-5" class="headerlink" title="步骤"></a>步骤</h3><ol>
<li><p>构造最大堆：若数组下标范围为1~n，考虑到单独一个元素是大根堆，则从下标n&#x2F;2+1开始的元素均为大根堆。于是只要从n&#x2F;2开始，向前依次构造大根堆，这样就能保证，构造到某个节点时，它的左右子树都已经是大根堆。</p>
</li>
<li><p>堆排序（HeapSort）：由于堆是用数组模拟的。得到一个大根堆后，数组内部并不是有序的。因此需要将堆化数组有序化。思想是移除根节点，并做最大堆调整的递归运算。第一次将heap[1]与heap[n]交换，再对heap[1…n-1]做最大堆调整。第二次将heap[1]与heap[n-1]交换，再对heap[1…n-2]做最大堆调整。重复该操作直至heap[1]和heap[2]交换。由于每次都是将最大的数并入到后面的有序区间，故操作完后整个数组就是有序的了。</p>
</li>
<li><p>最大堆调整（sink）：该方法是提供给上述两个过程调用的。目的是将堆的末端子节点作调整，使得子节点永远小于父节点 。</p>
</li>
</ol>
<h3 id="代码-7"><a href="#代码-7" class="headerlink" title="代码"></a>代码</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">heap_sort</span>(<span class="params">array</span>):</span><br><span class="line">    <span class="comment"># 此处用最大堆</span></span><br><span class="line">    n = <span class="built_in">len</span>(array)</span><br><span class="line">    pq = [<span class="literal">None</span>] + array</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">sink</span>(<span class="params">k</span>):  <span class="comment"># 下沉</span></span><br><span class="line">        <span class="keyword">while</span> <span class="number">2</span>*k &lt;= n:</span><br><span class="line">            j = <span class="number">2</span>*k </span><br><span class="line">            <span class="keyword">if</span> j &lt; n <span class="keyword">and</span> pq[j] &lt; pq[j+<span class="number">1</span>]:</span><br><span class="line">                j += <span class="number">1</span></span><br><span class="line">            <span class="keyword">if</span> pq[k] &gt;= pq[j]:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            pq[k], pq[j] = pq[j], pq[k]</span><br><span class="line">            k = j</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> k <span class="keyword">in</span> xrange(n/<span class="number">2</span>, <span class="number">0</span>, -<span class="number">1</span>):</span><br><span class="line">        sink(k)</span><br><span class="line">    <span class="keyword">while</span> n &gt; <span class="number">1</span>:</span><br><span class="line">        pq[<span class="number">1</span>], pq[n] = pq[n], pq[<span class="number">1</span>]</span><br><span class="line">        n -= <span class="number">1</span></span><br><span class="line">        sink(<span class="number">1</span>)</span><br><span class="line">    <span class="keyword">return</span> pq[<span class="number">1</span>:]</span><br></pre></td></tr></table></figure>

<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>下面为以上八种排序算法指标对比情况：</p>
<table>
<thead>
<tr>
<th>排序方法</th>
<th>平均情况</th>
<th>最好情况</th>
<th>最坏情况</th>
<th>辅助空间</th>
<th>稳定性</th>
</tr>
</thead>
<tbody><tr>
<td>冒泡排序</td>
<td>〇(n^2)</td>
<td>〇(n)</td>
<td>〇(n^2)</td>
<td>〇(1)</td>
<td>稳定</td>
</tr>
<tr>
<td>选择排序</td>
<td>〇(n^2)</td>
<td>〇(n^2)</td>
<td>〇(n^2)</td>
<td>〇(1)</td>
<td>不稳定</td>
</tr>
<tr>
<td>插入排序</td>
<td>〇(n^2)</td>
<td>〇(n)</td>
<td>〇(n^2)</td>
<td>〇(1)</td>
<td>稳定</td>
</tr>
<tr>
<td>希尔排序</td>
<td>〇(nlog(n))~〇(n^2)</td>
<td>〇(n^1.3)</td>
<td>〇(n^2)</td>
<td>〇(1)</td>
<td>不稳定</td>
</tr>
<tr>
<td>堆排序</td>
<td>〇(nlog(n))</td>
<td>〇(nlog(n))</td>
<td>〇(nlog(n))</td>
<td>〇(1)</td>
<td>不稳定</td>
</tr>
<tr>
<td>归并排序</td>
<td>〇(nlog(n))</td>
<td>〇(nlog(n))</td>
<td>〇(nlog(n))</td>
<td>〇(n)</td>
<td>稳定</td>
</tr>
<tr>
<td>快速排序</td>
<td>〇(nlog(n))</td>
<td>〇(nlog(n))</td>
<td>〇(n^2)</td>
<td>〇(log(n))~〇(n)</td>
<td>不稳定</td>
</tr>
<tr>
<td>桶排序</td>
<td>〇(n+k)</td>
<td>〇(n+k)</td>
<td>〇(n^2)</td>
<td>〇(n)</td>
<td>稳定</td>
</tr>
</tbody></table>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>用Pytest测试Python代码</title>
    <url>/2020/03/12/python-test-suit-pytest/</url>
    <content><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>pytest是一个非常成熟的全功能的Python测试框架, 简单灵活, 容易上手, 具有很多第三方插件，并且可以自定义扩展.</p>
<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line">pip <span class="keyword">install</span> pytest</span><br></pre></td></tr></table></figure>

<h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="简单例子"><a href="#简单例子" class="headerlink" title="简单例子"></a>简单例子</h3><p>先写个测试代码<code>tmp.py</code></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">add</span>(<span class="params">a, b</span>):</span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_add</span>():</span><br><span class="line">    <span class="keyword">assert</span> add(<span class="number">1</span>, <span class="number">1</span>) == <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_add_fail</span>():</span><br><span class="line">    <span class="keyword">assert</span> add(<span class="number">1</span>, <span class="number">2</span>) == <span class="number">2</span></span><br></pre></td></tr></table></figure>

<p>使用方法</p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">usage: py<span class="selector-class">.test</span> <span class="selector-attr">[options]</span> <span class="selector-attr">[file_or_dir]</span> <span class="selector-attr">[file_or_dir]</span> <span class="selector-attr">[...]</span></span><br></pre></td></tr></table></figure>

<span id="more"></span>

<p>所以我们执行 <code>pytest tmp.py</code></p>
<p>输出结果, 两个测试用例一个通过一个失败, 通过用绿色 <font color="green">.</font> 表示, 失败红色 <font color="red">F</font> 表示.</p>
<figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line">=============================== test session starts ===============================</span><br><span class="line">platform darwin -- Python 3.6.9, pytest-5.2.0, py-1.8.0, pluggy-0.13.0</span><br><span class="line">rootdir: /Users/ruan/projects</span><br><span class="line">plugins: pylint-0.15.1, mypy-0.5.0, celery-4.3.0</span><br><span class="line">collected 2 items</span><br><span class="line"></span><br><span class="line">tmp.py .F                                                                   [100%]</span><br><span class="line"></span><br><span class="line">==================================== FAILURES =====================================</span><br><span class="line"><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span>__ test<span class="emphasis">_add_</span>fail <span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span><span class="strong">____</span>__</span><br><span class="line"></span><br><span class="line"><span class="code">    def test_add_fail():</span></span><br><span class="line"><span class="code">&gt;       assert add(1, 2) == 2</span></span><br><span class="line"><span class="code">E       assert 3 == 2</span></span><br><span class="line"><span class="code">E        +  where 3 = add(1, 2)</span></span><br><span class="line"><span class="code"></span></span><br><span class="line">tmp.py:92: AssertionError</span><br><span class="line">=========================== 1 failed, 1 passed in 0.10s ===========================</span><br></pre></td></tr></table></figure>

<h3 id="用例查找规则"><a href="#用例查找规则" class="headerlink" title="用例查找规则"></a>用例查找规则</h3><p>测试用例目录优先级: <code>命令行参数目录 &gt; 配置文件中的testpaths配置项 &gt; 当前目录</code></p>
<p>支持的配置文件: pytest.ini，tox.ini，setup.cfg</p>
<p>测试用例查找规则:</p>
<ul>
<li>如果当前目录在包中, 则以该包的顶级目录作为工作目录(向上查找, 第一个不包含<code>__init__.py</code>的目录)</li>
<li>递归遍历目录，除非目录指定了不递归参数<code>norecursedirs</code></li>
<li>在目录中查找匹配<code>test_*.py</code> 或者 <code>*_test.py</code> 的文件, 并以包名的全路径导入</li>
<li>查找以 <code>Test</code> 开头的类(该类不能有 init 方法), 的以<code>test</code>为前缀的方法.</li>
<li>查找以<code>test</code>为前缀的函数.</li>
</ul>
<h3 id="常用参数"><a href="#常用参数" class="headerlink" title="常用参数"></a>常用参数</h3><figure class="highlight livecodeserver"><table><tr><td class="code"><pre><span class="line">pytest <span class="comment">--fixtures, --funcargs  查看可用的 fixtures</span></span><br><span class="line">pytest <span class="comment">--markers                查看可用的 markers</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 失败后停止</span></span><br><span class="line">pytest -x           首次失败后停止执行</span><br><span class="line">pytest <span class="comment">--maxfail=2  两次失败之后停止执行</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 调试输出</span></span><br><span class="line">pytest -l, <span class="comment">--showlocals  在 traceback 中显示本地变量</span></span><br><span class="line">pytest -q, <span class="comment">--quiet       静默模式输出</span></span><br><span class="line">pytest -v, <span class="comment">--verbose     输出更详细的信息</span></span><br><span class="line">pytest -s                捕获输出, 例如显示 print 函数的输出</span><br><span class="line">pytest <span class="comment">--tb=style        错误信息输出格式</span></span><br><span class="line">    - <span class="keyword">long</span>    默认的traceback信息格式化形式</span><br><span class="line">    - native  标准库格式化形式</span><br><span class="line">    - <span class="keyword">short</span>   更短的格式</span><br><span class="line">    - <span class="built_in">line</span>    每个错误一行</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行指定 marker 的测试</span></span><br><span class="line">pytest -m MARKEXPR</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行匹配的测试(函数名称)</span></span><br><span class="line">pytest -k stringexpr</span><br><span class="line"></span><br><span class="line"><span class="comment"># 只收集并显示可用的测试用例，但不运行测试用例</span></span><br><span class="line">pytest <span class="comment">--collect-only</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 失败时调用 PDB</span></span><br><span class="line">pytest <span class="comment">--pdb</span></span><br></pre></td></tr></table></figure>

<p><strong>使用示例</strong></p>
<ul>
<li><p>执行单个模块中的全部用例: <code>py.test test_mod.py</code></p>
</li>
<li><p>执行指定路径下的全部用例: <code>py.test somepath</code></p>
</li>
<li><p>执行匹配<code>stringexpr</code>表达式的用例: <code>py.test -k stringexpr</code></p>
</li>
<li><p>运行指定模块中的某个用例: <code>pytest test_mod.py::test_func</code></p>
</li>
<li><p>运行某个类下的某个用例: <code>pytest test_mod.py::TestClass::test_method</code></p>
</li>
<li><p>执行测试用例时输出print内容: <code>pytest -s test_mod.py</code></p>
</li>
</ul>
<h3 id="编写测试用例"><a href="#编写测试用例" class="headerlink" title="编写测试用例"></a>编写测试用例</h3><h4 id="断言"><a href="#断言" class="headerlink" title="断言"></a>断言</h4><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 简单断言</span></span><br><span class="line"><span class="attribute">assert</span> <span class="number">1</span> + <span class="number">1</span> == <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 断言发生异常</span></span><br><span class="line"><span class="attribute">with</span> pytest.raises(ZeroDivisionError):</span><br><span class="line">    <span class="attribute">1</span> / <span class="number">0</span></span><br></pre></td></tr></table></figure>

<h4 id="Fixtures"><a href="#Fixtures" class="headerlink" title="Fixtures"></a>Fixtures</h4><p><code>fixture</code> 是 pytest 特有的功能，它用 pytest.fixture 标识，定义在函数前面, 起到依赖注入的作用.</p>
<p>在编写测试函数的时候，可以将此函数名称做为传入参数，pytest 将会以依赖注入方式，将该函数的返回值作为测试函数的传入参数。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="comment"># fixtures documentation order example</span></span><br><span class="line">order = []</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">&quot;session&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">s1</span>():</span><br><span class="line">    order.append(<span class="string">&quot;s1&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">&quot;module&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">m1</span>():</span><br><span class="line">    order.append(<span class="string">&quot;m1&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">f1</span>(<span class="params">f3</span>):</span><br><span class="line">    order.append(<span class="string">&quot;f1&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">f3</span>():</span><br><span class="line">    order.append(<span class="string">&quot;f3&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">a1</span>():</span><br><span class="line">    order.append(<span class="string">&quot;a1&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">f2</span>():</span><br><span class="line">    order.append(<span class="string">&quot;f2&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">f1, m1, f2, s1</span>):</span><br><span class="line">    <span class="keyword">assert</span> order == [<span class="string">&quot;s1&quot;</span>, <span class="string">&quot;m1&quot;</span>, <span class="string">&quot;a1&quot;</span>, <span class="string">&quot;f3&quot;</span>, <span class="string">&quot;f1&quot;</span>, <span class="string">&quot;f2&quot;</span>]</span><br></pre></td></tr></table></figure>

<p>fixture接收<code>scope</code>和<code>autouse</code>参数<br>不同的scope表明了fixture的作用访问和执行先后顺序, 以下fixture执行顺序从上到下依次执行. 同级别是fixture按照依赖关系决定先后次序.<br>autouse参数决定了fixture是自动执行, 还是在被用做参数传入时才执行</p>
<ul>
<li><code>scope=&#39;session&#39;</code>: 会话级别, 测试开始时执行一次, 在整个测试的过程不变</li>
<li><code>scope=&#39;module&#39;</code>: 模块级别, 每个模块测试开始时执行一次, 在整个模块测试的过程不变</li>
<li><code>scope=&#39;function&#39;, autouse=True</code>: 函数级别, 在每个函数开始前自动执行.</li>
<li><code>scope=&#39;function&#39;</code>: 函数级别(默认是这个级别).</li>
</ul>
<h4 id="setUp-tearDown"><a href="#setUp-tearDown" class="headerlink" title="setUp &amp; tearDown"></a>setUp &amp; tearDown</h4><p><code>setup\teardown</code> 是指在模块、函数、类开始运行以及结束运行时执行一些动作。</p>
<p>例如: 数据库连接管理, 临时文件清理.</p>
<p>pytest支持的<code>setup\teardown</code>钩子</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 模块级别</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">setup_module</span>(<span class="params">module</span>):</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">teardown_module</span>(<span class="params">module</span>):</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 类级别</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestA</span>:</span><br><span class="line"><span class="meta">	@classmethod</span></span><br><span class="line">	<span class="keyword">def</span> <span class="title function_">setup_class</span>(<span class="params">cls</span>):</span><br><span class="line">	    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="meta">	@classmethod</span></span><br><span class="line">	<span class="keyword">def</span> <span class="title function_">teardown_class</span>(<span class="params">cls</span>):</span><br><span class="line">	    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">	<span class="comment"># 方法级别</span></span><br><span class="line">	<span class="keyword">def</span> <span class="title function_">setup_method</span>(<span class="params">self, method</span>):</span><br><span class="line">	    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">	<span class="keyword">def</span> <span class="title function_">teardown_method</span>(<span class="params">self, method</span>):</span><br><span class="line">	    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 函数级别</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">setup_function</span>(<span class="params">function</span>):</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">teardown_function</span>(<span class="params">function</span>):</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 会话级别</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pytest_sessionstart</span>(<span class="params">session</span>):</span><br><span class="line">    <span class="comment"># setup_stuff</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pytest_sessionfinish</span>(<span class="params">session, exitstatus</span>):</span><br><span class="line">    <span class="comment"># teardown_stuff</span></span><br></pre></td></tr></table></figure>

<p>以上这些钩子, 也都可以用 fixture 的方式等效实现, 例如：</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@fixture(<span class="params">scope=<span class="string">&#x27;session&#x27;</span>, autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">my_fixture</span>():</span><br><span class="line">    <span class="comment"># setup_stuff</span></span><br><span class="line">    <span class="keyword">yield</span></span><br><span class="line">    <span class="comment"># teardown_stuff</span></span><br></pre></td></tr></table></figure>

<h4 id="conftest-py"><a href="#conftest-py" class="headerlink" title="conftest.py"></a>conftest.py</h4><p>从广义理解，<code>conftest.py</code> 是一个本地的 <code>per-directory</code> 插件，在该文件中可以定义目录特定的 hooks 和 fixtures。</p>
<p><code>pytest</code> 框架会在它测试的项目中寻找 conftest.py 文件，然后在这个文件中寻找针对整个目录的测试选项.</p>
<p>总结起来，<code>conftest.py</code> 文件大致有如下几种功能：</p>
<ul>
<li><p><strong>Fixtures:</strong> 用于给测试用例提供静态的测试数据，其可以被所有的测试用于访问，除非指定了范围</p>
</li>
<li><p><strong>加载插件:</strong> 用于导入外部插件或模块:</p>
</li>
</ul>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="attr">pytest_plugins</span> =<span class="string">&quot;myapp.testsupport.myplugin&quot;</span></span><br></pre></td></tr></table></figure>

<ul>
<li><strong>测试根路径:</strong> 如果将 conftest.py 文件放在项目根路径中，则 pytest 会自己搜索项目根目录下的子模块，并加入到 sys.path 中，这样便可以对项目中的所有模块进行测试，而不用设置 PYTHONPATH 来指定项目模块的位置。</li>
</ul>
<h4 id="Markers"><a href="#Markers" class="headerlink" title="Markers"></a>Markers</h4><p><code>marker</code> 的作用是，用来标记测试，以便于选择性的执行测试用例。</p>
<p>Pytest 提供了一些内建的 marker, 这里列了几个个人觉得有用的, 详细的请看<a href="https://docs.pytest.org/en/latest/mark.html">文档</a></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 跳过测试</span></span><br><span class="line"><span class="meta">@pytest.mark.skip(<span class="params">reason=<span class="literal">None</span></span>)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 满足某个条件时跳过该测试</span></span><br><span class="line"><span class="meta">@pytest.mark.skipif(<span class="params">condition</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 让测试尽早地被执行</span></span><br><span class="line"><span class="meta">@pytest.mark.tryfirst</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 让测试尽量晚执行</span></span><br><span class="line"><span class="meta">@pytest.mark.trylast</span></span><br></pre></td></tr></table></figure>

<p>例子:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 如果是window平台, 跳过该测试用例</span></span><br><span class="line"><span class="meta">@pytest.mark.skipif(<span class="params">sys.platform == <span class="string">&quot;win32&quot;</span>, reason=<span class="string">&quot;does not run on windows&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestPosixCalls</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">test_function</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;will not be setup or run under &#x27;win32&#x27; platform&quot;</span></span><br></pre></td></tr></table></figure>

<p>也可以自定义markers, 这些markers只是具有名称, 只起了标记作用, 通过<code>-m</code>参数执行指定的marker的测试用例.</p>
<p>例如<code>pytest -m hello</code>, 只执行test_one测试用例</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@pytest.mark.hello</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_one</span>():</span><br><span class="line">    <span class="keyword">assert</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.mark.world</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_two</span>():</span><br><span class="line">    <span class="keyword">assert</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure>

<h3 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h3><p>通过pip安装, 例如pylint插件</p>
<figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line">pip <span class="keyword">install</span> pytest-pylint</span><br></pre></td></tr></table></figure>

<p>通过添加参数来使用插件</p>
<figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">pytest <span class="comment">--pylint tmp.py</span></span><br></pre></td></tr></table></figure>

<p>常用插件</p>
<ul>
<li>pytest-randomly: 测试顺序随机</li>
<li>pytest-xdist: 分布式测试</li>
<li>pytest-cov: 生成测试覆盖率报告</li>
<li>pytest-pep8: 检测代码是否符合 PEP8 规范</li>
<li>pytest-pylint: 检测代码风格和错误</li>
<li>pytest-html: 生成 html 报告</li>
<li>pytest-rerunfailures: 失败重试</li>
<li>pytest-timeout: 超时测试</li>
<li>pytest-mypy: type hints 检查</li>
</ul>
<h3 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h3><p>可以在项目根目录放置<code>pytest.ini</code>来控制pytest的行为</p>
<p>例如:</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[pytest]</span></span><br><span class="line"><span class="attr">addopts</span> = --pylint --mypy <span class="comment">; 指定默认追加的参数</span></span><br><span class="line"><span class="comment">; testpaths = /home/test/ ; 测试用例路径</span></span><br><span class="line"><span class="comment">; minversion = 1.1 ; 依赖的pytest的最低版本</span></span><br><span class="line"><span class="comment">; norecursedirs = xx ; 不搜索测试用例的路径</span></span><br><span class="line"><span class="comment">; console_output_style ; 控制台测试报告格式</span></span><br><span class="line"><span class="comment">; 记录测试用例中用到的markers, 通过--markers参数可以显示出来</span></span><br><span class="line"><span class="comment">; markers =</span></span><br><span class="line"><span class="comment">;   webtest:  Run the webtest case</span></span><br><span class="line"><span class="comment">;   hello: Run the hello case</span></span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://docs.pytest.org/en/latest/">https://docs.pytest.org/en/latest/</a></li>
<li><a href="http://blog.konghy.cn/2018/05/08/pytest/">http://blog.konghy.cn/2018/05/08/pytest/</a></li>
<li><a href="https://www.jianshu.com/p/a613a3a4d030">https://www.jianshu.com/p/a613a3a4d030</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Pytest</tag>
      </tags>
  </entry>
  <entry>
    <title>使用line_profiler分析Python程序耗时情况</title>
    <url>/2020/09/15/python-time-consuming-analysis-with-line_profiler/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>最近发生了一个服务故障，在一个同事对日志服务做了改动后，服务的耗时就变大了很多，数据大量积压。<br>通过观察监控报表，我们发现从他的改动上线后，服务器的CPU使用率大幅增加，基本处于满载状态。<br>粗略审查代码并没有发现问题，我们只能紧急回滚了服务，日志消费也恢复到正常状态了。<br>但是根本问题还没有找到，所以我请出了line_profiler来分析程序的具体耗时情况。</p>
<p><a href="https://github.com/pyutils/line_profiler">line_profiler</a>是个代码耗时分析器，可以逐行分析代码的耗时情况。</p>
<span id="more"></span>

<h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>line_profiler包含两个部分</p>
<ul>
<li>kernprof：代码执行和耗时记录</li>
<li>line_profiler：耗时报告解析和展示</li>
</ul>
<p>首先，安装：<code>pip install line_profiler -U</code></p>
<p>给需要分析的代码加上<code>@profile</code>装饰器, <code>@profile</code>装饰器不需要被import，kernprof在运行时会注入依赖，kernprof会记录被装饰函数的耗时情况，有多个函数也可以都加上装饰器。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">@profile</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>(<span class="params">run_type</span>):</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;data.txt&#x27;</span>,<span class="string">&#x27;r&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">        data=f.readline()</span><br><span class="line">    input_data = json.loads(data)</span><br><span class="line">    source = input_data.get(<span class="string">&#x27;source&#x27;</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    service = input_data.get(<span class="string">&#x27;service&#x27;</span>, <span class="number">0</span>)</span><br><span class="line">    apply_no = input_data.get(<span class="string">&#x27;applyNo&#x27;</span>, <span class="number">0</span>)</span><br><span class="line">    info = input_data.get(<span class="string">&#x27;info&#x27;</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    cust_no = input_data.get(<span class="string">&#x27;cust_no&#x27;</span>, <span class="number">0</span>)</span><br><span class="line">    job.run(source, service, apply_no, info, run_type)</span><br></pre></td></tr></table></figure>

<p>然后，运行代码：<code>kernprof -l service.py</code>, 统计耗时情况, 执行完成后生成耗时报告文件<code>service.py.lprof</code></p>
<p>最后，耗时报告解析：<code>python -m line_profiler service.py.lprof</code>，会生成可阅读的耗时报告。</p>
<h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>耗时报告示例</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">Timer unit: 1e-06 s</span><br><span class="line"></span><br><span class="line">Total time: 4.18908 s</span><br><span class="line">File: service.py</span><br><span class="line">Function: main at line 38</span><br><span class="line"></span><br><span class="line">Line #      Hits         Time  Per Hit   % Time  Line Contents</span><br><span class="line">==============================================================</span><br><span class="line">    38                                               @profile</span><br><span class="line">    39                                               def main(run_type):</span><br><span class="line">    40         1         45.0     45.0      0.0          with open(<span class="string">&#x27;data.txt&#x27;</span>,<span class="string">&#x27;r&#x27;</span>) as f:</span><br><span class="line">    41         1        517.0    517.0      0.0              <span class="attribute">data</span>=f.readline()</span><br><span class="line">    42         1       6564.0   6564.0      0.2          input_data = json.loads(data)</span><br><span class="line">    43         1         20.0     20.0      0.0          source = input_data.<span class="built_in">get</span>(<span class="string">&#x27;source&#x27;</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    44         1          6.0      6.0      0.0         <span class="built_in"> service </span>= input_data.<span class="built_in">get</span>(<span class="string">&#x27;service&#x27;</span>, 0</span><br><span class="line">    45         1          5.0      5.0      0.0          apply_no = input_data.<span class="built_in">get</span>(<span class="string">&#x27;applyNo&#x27;</span>, </span><br><span class="line">    46         1          5.0      5.0      0.0          <span class="built_in">info</span> = input_data.<span class="built_in">get</span>(<span class="string">&#x27;info&#x27;</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    47</span><br><span class="line">    48         1          1.0      1.0      0.0          cust_no = input_data.<span class="built_in">get</span>(<span class="string">&#x27;cust_no&#x27;</span>, 0)</span><br><span class="line">    49                                                   </span><br><span class="line">    50         1    4181914.0 4181914.0     99.8         job.<span class="built_in">run</span>(source, service, apply_no, info, run_type)</span><br></pre></td></tr></table></figure>

<p>耗时报告会逐行显示代码耗时和调用情况</p>
<ul>
<li>Timer unit: 耗时单位，这里是微秒</li>
<li>Total time：总耗时</li>
<li>Line #：行号</li>
<li>Hits：函数被调用次数</li>
<li>Time：函数总耗时</li>
<li>Per Hit：函数每次调用耗时</li>
<li>% Time：耗时占比</li>
</ul>
<p>可以看到，总耗时4.18908秒，<code>job.run</code>占用了<code>99.8%</code>的时间。</p>
<p>接下来我们分析下<code>job.run</code>的耗时情况</p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">Timer unit: <span class="number">1e-06</span> s</span><br><span class="line"></span><br><span class="line">Total <span class="type">time</span>: <span class="number">4.16909</span> s</span><br><span class="line">File: job.py</span><br><span class="line"><span class="keyword">Function</span>: <span class="keyword">inout</span> <span class="keyword">at</span> line <span class="number">69</span></span><br><span class="line"></span><br><span class="line">Line #      Hits         <span class="type">Time</span>  <span class="keyword">Per</span> Hit   <span class="operator">%</span> <span class="type">Time</span>  Line Contents</span><br><span class="line"><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span></span><br><span class="line">    <span class="number">69</span>                                               <span class="variable">@profile</span></span><br><span class="line">    <span class="number">70</span>                                               def run(self, apply_no, info, req_type, run_type):</span><br><span class="line">    ... 省略部分逻辑</span><br><span class="line">   <span class="number">112</span>         <span class="number">1</span>          <span class="number">2.0</span>      <span class="number">2.0</span>      <span class="number">0.0</span>          if req_type <span class="operator">=</span><span class="operator">=</span> <span class="string">&#x27;credit&#x27;</span>:</span><br><span class="line">   <span class="number">113</span>         <span class="number">1</span>    <span class="number">3370673.0</span> <span class="number">3370673.0</span>     <span class="number">80.8</span>              <span class="keyword">sql</span> <span class="operator">=</span> db_utils.generate_sql(info1, <span class="keyword">sql</span>, val, columns)</span><br><span class="line">   <span class="number">114</span>                                                   <span class="keyword">else</span>:</span><br><span class="line">   <span class="number">115</span>                                                       <span class="keyword">sql</span> <span class="operator">=</span> db_utils.generate_sql(info1, <span class="keyword">sql</span>, val, columns)</span><br><span class="line">   <span class="number">116</span>         <span class="number">1</span>       <span class="number">6948.0</span>   <span class="number">6948.0</span>      <span class="number">0.2</span>          safe3_db.executor(<span class="keyword">sql</span>)</span><br></pre></td></tr></table></figure>

<p>可以看到，113行的生成SQL语句占用了80%的时间，SQL反倒不怎么耗时，显然是不正常的。</p>
<p>再看下<code>db_utils.generate_sql</code>函数的具体代码<br>可以发现遍历字段列表(item_list)的时候，调用了<code>get_json_value</code>，该函数每次都会调用<code>copy.deepcopy</code>。<br>众所周知，对象的深拷贝是十分消耗cpu资源的，并且这里深拷贝的次数会随着字段数的增加而线性增长。</p>
<p>把深拷贝行为去除后，服务耗时和cpu占用都回归到了正常水准。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">generate_sql</span>(<span class="params">info, sql, val, item_list</span>):</span><br><span class="line">    index = <span class="number">0</span></span><br><span class="line">    length = <span class="built_in">len</span>(item_list)</span><br><span class="line">    <span class="keyword">for</span> item <span class="keyword">in</span> item_list:</span><br><span class="line">        index += <span class="number">1</span></span><br><span class="line">        buf = get_json_value(info,item)</span><br><span class="line">        <span class="keyword">if</span> index &lt; length:</span><br><span class="line">            sql = sql + <span class="string">&#x27;, `&#x27;</span> + <span class="built_in">str</span>(item) + <span class="string">&#x27;`&#x27;</span></span><br><span class="line">            val = val + <span class="string">&#x27;, \&#x27;&#x27;</span> + MySQLdb.escape_string(<span class="built_in">str</span>(buf)) + <span class="string">&#x27;\&#x27;&#x27;</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            sql = sql + <span class="string">&#x27;, `&#x27;</span> + <span class="built_in">str</span>(item) + <span class="string">&#x27;`) &#x27;</span></span><br><span class="line">            val = val + <span class="string">&#x27;, \&#x27;&#x27;</span> + MySQLdb.escape_string(<span class="built_in">str</span>(buf)) + <span class="string">&#x27;\&#x27;)&#x27;</span></span><br><span class="line">    <span class="keyword">return</span> sql + val</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_json_value</span>(<span class="params">js,term</span>):</span><br><span class="line">    tsplit=term.split(<span class="string">&#x27;|&#x27;</span>)</span><br><span class="line">    result=<span class="string">&#x27;&#x27;</span></span><br><span class="line">    buf=copy.deepcopy(js)</span><br><span class="line">    <span class="comment"># 省略逻辑代码</span></span><br><span class="line">    <span class="keyword">return</span> value</span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python 类型标记(Type Hints) 初探</title>
    <url>/2020/03/11/python-type-hints-guide/</url>
    <content><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>Python是一门动态强类型语言, 动态性是它鲜明的特点. </p>
<p>但是动态性在给程序员充分的自由的同时, 也带来了一些不好的负面效应. 特别是在团队协作的时候, 不好的队友会引发许多难以定位的问题.</p>
<p>同时动态性也大大削弱了ide的作用, 代码提示, 重构等一些功能远不如静态语言来得可靠.</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name, age</span>):</span><br><span class="line">        <span class="variable language_">self</span>.name = name</span><br><span class="line">        <span class="variable language_">self</span>.age = age</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">introduce_someone</span>(<span class="params">person</span>):</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;&#123;&#125; is &#123;&#125; years old&#x27;</span>.<span class="built_in">format</span>(person.name, person.age))</span><br></pre></td></tr></table></figure>

<p>比如这个代码片段, ide很难准确识别<code>introduce_someone</code>的参数应该是<code>Person</code>类的实例, 它只能单纯地从文本上分析, 并把所有可能的单词都提示出来. </p>
<p>而且当调用<code>introduce_someone</code>, 传入了不合适的对象, 也很难通过静态检查发现.</p>
<p>类型标记的出现就解决了这些问题.</p>
<span id="more"></span>

<h2 id="性空"><a href="#性空" class="headerlink" title="性空"></a>性空</h2><p><strong>类型标记</strong>就是, 给变量, 参数, 函数附加上类型信息. 类似Java等静态语言的变量声明信息.</p>
<p>Python 从3.5开始, 引入了类型标记系统, 并在后面的版本有所增强.</p>
<p>类型标记的基本语法 <code>变量名: 标记</code>, 标记可以是字符串, 对象或者<code>Type aliases</code>(类型别名)</p>
<h3 id="变量类型标记"><a href="#变量类型标记" class="headerlink" title="变量类型标记"></a>变量类型标记</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">name: <span class="built_in">str</span> = <span class="string">&#x27;tom&#x27;</span></span><br><span class="line">age: <span class="string">&#x27;int&#x27;</span> = <span class="number">42</span></span><br></pre></td></tr></table></figure>

<h3 id="函数类型标记"><a href="#函数类型标记" class="headerlink" title="函数类型标记"></a>函数类型标记</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name: <span class="built_in">str</span>, age: <span class="built_in">int</span></span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">        <span class="variable language_">self</span>.name = name</span><br><span class="line">        <span class="variable language_">self</span>.age = age</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">introduce_someone</span>(<span class="params">person: <span class="string">&#x27;Person&#x27;</span></span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;&#123;&#125; is &#123;&#125; years old&#x27;</span>.<span class="built_in">format</span>(person.name, person.age))</span><br><span class="line"></span><br><span class="line">introduce_someone(<span class="string">&#x27;fuck&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>函数的类型标记比变量多了一项: <strong>返回值</strong>, 通过<code>-&gt;</code>与函数名连接在一起.</p>
<p>如果调用<code>introduce_someone</code>, 参数不是<code>Person</code>类的实例. 静态检查会发现以下错误.</p>
<figure class="highlight subunit"><table><tr><td class="code"><pre><span class="line"><span class="keyword">error: </span>Argument 1 to &quot;introduce_someone&quot; has incompatible type &quot;str&quot;; expected &quot;Person&quot;</span><br><span class="line">Found 1 error in 1 file (checked 1 source file)</span><br></pre></td></tr></table></figure>

<h3 id="mypy"><a href="#mypy" class="headerlink" title="mypy"></a>mypy</h3><p><a href="http://mypy-lang.org/">mypy</a> 实用工具是一款针对 Python 的静态类型检查程序, 也可以和pytest一起配合使用.</p>
<h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line">pip <span class="keyword">install</span> mypy</span><br></pre></td></tr></table></figure>

<h4 id="执行检查"><a href="#执行检查" class="headerlink" title="执行检查"></a>执行检查</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mypy my_program.py my_src_folder</span><br></pre></td></tr></table></figure>

<h3 id="typing库"><a href="#typing库" class="headerlink" title="typing库"></a>typing库</h3><p>Python内置typing库提供了许多有用的工具来辅助类型标记</p>
<h4 id="类型别名-Type-aliases"><a href="#类型别名-Type-aliases" class="headerlink" title="类型别名 Type aliases"></a>类型别名 Type aliases</h4><p>类型别名(运行时的标识函数), 帮助更好地进行类型标记, 这些别名可以进行组合.</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">List</span>, <span class="type">Dict</span>, <span class="type">Tuple</span>, <span class="type">Sequence</span></span><br><span class="line"></span><br><span class="line">Vector = <span class="type">List</span>[<span class="built_in">float</span>]   <span class="comment"># [float, float, ...]</span></span><br><span class="line">ConnectionOptions = <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="built_in">str</span>]  <span class="comment"># &#123;str: str, ...&#125;</span></span><br><span class="line">Address = <span class="type">Tuple</span>[<span class="built_in">str</span>, <span class="built_in">int</span>]  <span class="comment"># (str, int)</span></span><br><span class="line">Server = <span class="type">Tuple</span>[Address, ConnectionOptions]  <span class="comment"># ((str, int), &#123;str: str, ...&#125;)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">print_vector</span>(<span class="params">v: Vector</span>):</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> v:</span><br><span class="line">        <span class="built_in">print</span>(i)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">print_connectionoptions</span>(<span class="params">c: ConnectionOptions</span>):</span><br><span class="line">    <span class="keyword">for</span> k, v <span class="keyword">in</span> c.items():</span><br><span class="line">        <span class="built_in">print</span>(k, v)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">print_address</span>(<span class="params">a: Address</span>):</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;&#123;&#125;:&#123;&#125;&#x27;</span>.<span class="built_in">format</span>(a[<span class="number">0</span>], a[<span class="number">1</span>]))</span><br><span class="line"></span><br><span class="line">print_vector([<span class="string">&#x27;1&#x27;</span>, <span class="number">1</span>])</span><br><span class="line">print_connectionoptions(&#123;<span class="string">&#x27;a&#x27;</span>: <span class="number">1</span>&#125;)</span><br><span class="line">print_address([<span class="string">&#x27;127.0.0.1&#x27;</span>, <span class="number">22</span>])</span><br></pre></td></tr></table></figure>

<p>静态检查结果, 三个调用错误都发现了</p>
<figure class="highlight subunit"><table><tr><td class="code"><pre><span class="line"><span class="keyword">error: </span>List item 0 has incompatible type &quot;str&quot;; expected &quot;float&quot;</span><br><span class="line"><span class="keyword">error: </span>Dict entry 0 has incompatible type &quot;str&quot;: &quot;int&quot;; expected &quot;str&quot;: &quot;str&quot;</span><br><span class="line"><span class="keyword">error: </span>Argument 1 to &quot;print_address&quot; has incompatible type &quot;List[object]&quot;; expected &quot;Tuple[str, int]&quot;</span><br><span class="line"></span><br><span class="line">Found 3 errors in 1 file (checked 1 source file)</span><br></pre></td></tr></table></figure>

<h4 id="NewType"><a href="#NewType" class="headerlink" title="NewType"></a>NewType</h4><p>使用 NewType() 辅助函数创建派生的类型标记.<br>静态类型检查器会将新类型视为它是原始类型的子类, 可用于发现逻辑错误(比如: 虽然都是数字, 其实含义不同)</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> NewType</span><br><span class="line"></span><br><span class="line">UserId = NewType(<span class="string">&#x27;UserId&#x27;</span>, <span class="built_in">int</span>)</span><br><span class="line">some_id = UserId(<span class="number">524313</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_user_name</span>(<span class="params">user_id: UserId</span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line"><span class="comment"># pass typechecks</span></span><br><span class="line">user_a = get_user_name(some_id)</span><br><span class="line"></span><br><span class="line"><span class="comment"># does not pass typecheck; an int is not a UserId</span></span><br><span class="line">user_b = get_user_name(-<span class="number">1</span>)</span><br></pre></td></tr></table></figure>

<h4 id="Callable"><a href="#Callable" class="headerlink" title="Callable"></a>Callable</h4><p>标记为可调用对象, 期望特定签名的回调函数的框架可以将类型标注为 Callable[[Arg1Type, Arg2Type], ReturnType]。</p>
<p>例如:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Callable</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">feeder</span>(<span class="params">get_next_item: <span class="type">Callable</span>[[], <span class="built_in">str</span>]</span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    <span class="comment"># Body</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">async_query</span>(<span class="params">on_success: <span class="type">Callable</span>[[<span class="built_in">int</span>], <span class="literal">None</span>],</span></span><br><span class="line"><span class="params">                on_error: <span class="type">Callable</span>[[<span class="built_in">int</span>, Exception], <span class="literal">None</span>]</span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    <span class="comment"># Body</span></span><br></pre></td></tr></table></figure>

<h4 id="TypeVar"><a href="#TypeVar" class="headerlink" title="TypeVar"></a>TypeVar</h4><p>通过TypeVar来定义一个泛型类型标记, 限制对象的可选类型</p>
<figure class="highlight julia"><table><tr><td class="code"><pre><span class="line">from typing <span class="keyword">import</span> <span class="built_in">TypeVar</span></span><br><span class="line"></span><br><span class="line">T = <span class="built_in">TypeVar</span>(<span class="string">&#x27;T&#x27;</span>)  <span class="comment"># Can be anything</span></span><br><span class="line">A = <span class="built_in">TypeVar</span>(<span class="string">&#x27;A&#x27;</span>, str, bytes)  <span class="comment"># Must be str or bytes</span></span><br></pre></td></tr></table></figure>

<h4 id="Any-类型"><a href="#Any-类型" class="headerlink" title="Any 类型"></a>Any 类型</h4><p>Any 是一种特殊的类型。</p>
<p>静态类型检查器将所有类型视为与 Any 兼容，反之亦然， Any 也与所有类型相兼容。</p>
<p>所有返回值无类型或形参无类型的函数将隐式地默认使用 Any 类型(没有类型标记的代码, 模式标记就是Any)</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Any</span></span><br><span class="line"></span><br><span class="line">a = <span class="literal">None</span>    <span class="comment"># type: <span class="type">Any</span></span></span><br><span class="line">a = []      <span class="comment"># OK</span></span><br><span class="line">a = <span class="number">2</span>       <span class="comment"># OK</span></span><br><span class="line"></span><br><span class="line">s = <span class="string">&#x27;&#x27;</span>      <span class="comment"># type: <span class="built_in">str</span></span></span><br><span class="line">s = a       <span class="comment"># OK</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">foo</span>(<span class="params">item: <span class="type">Any</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="comment"># Typechecks; &#x27;item&#x27; could be any type,</span></span><br><span class="line">    <span class="comment"># and that type might have a &#x27;bar&#x27; method</span></span><br><span class="line">    item.bar()</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure>

<h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><ul>
<li>分离的hint信息（typing stubs） <a href="https://peps.python.org/pep-0561/">https://peps.python.org/pep-0561/</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python Web开发总结</title>
    <url>/2019/09/28/python-web-development/</url>
    <content><![CDATA[<h2 id="HTTP协议"><a href="#HTTP协议" class="headerlink" title="HTTP协议"></a>HTTP协议</h2><h3 id="HTTP简介"><a href="#HTTP简介" class="headerlink" title="HTTP简介"></a>HTTP简介</h3><p>HTTP协议是Hyper Text Transfer Protocol（超文本传输协议）的缩写。<br>用于从万维网（WWW:World Wide Web ）服务器传输超文本到本地浏览器的传送协议。</p>
<p>HTTP协议工作于客户端-服务端架构为上。<br>浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。<br>Web服务器根据接收到的请求后，向客户端发送响应信息。</p>
<p><img data-src="https://image.ponder.work/mweb/2019-09-28-15299338545214.jpg"></p>
<h4 id="主要特点"><a href="#主要特点" class="headerlink" title="主要特点"></a>主要特点</h4><ul>
<li><p><strong>无连接</strong>：无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求，并收到客户的应答后，即断开连接。采用这种方式可以节省传输时间。</p>
</li>
<li><p><strong>无状态</strong>：HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息，则它必须重传，这样可能导致每次连接传送的数据量增大。另一方面，在服务器不需要先前信息时它的应答就较快。</p>
</li>
</ul>
<span id="more"></span>

<h4 id="URL"><a href="#URL" class="headerlink" title="URL"></a>URL</h4><ul>
<li>URI: 统一资源标识符（Uniform Resource Identifiers, URI）</li>
<li>URL是一种特殊类型的URI</li>
</ul>
<p>HTTP使用统一资源标识符（Uniform Resource Identifiers, URI）来传输数据和建立连接。URL是一种特殊类型的URI，包含了用于查找某个资源的足够的信息</p>
<h3 id="HTTP报文"><a href="#HTTP报文" class="headerlink" title="HTTP报文"></a>HTTP报文</h3><h4 id="请求报文"><a href="#请求报文" class="headerlink" title="请求报文"></a>请求报文</h4><p>HTTP 协议是以 ASCII 码传输，建立在 TCP&#x2F;IP 协议之上的应用层规范。</p>
<p>规范把 HTTP 请求分为三个部分：状态行、请求头、消息主体。类似于下面这样：</p>
<figure class="highlight oxygene"><table><tr><td class="code"><pre><span class="line">&lt;<span class="keyword">method</span>&gt; &lt;<span class="title function_">request</span>-<span class="title function_">URL</span>&gt; &lt;<span class="title function_">version</span>&gt;</span><br><span class="line">&lt;<span class="title function_">headers</span>&gt;</span><br><span class="line"></span><br><span class="line">&lt;<span class="title function_">entity</span>-<span class="title function_">body</span>&gt;</span><br></pre></td></tr></table></figure>
<p><img data-src="https://image.ponder.work/mweb/2019-09-28-15299956241673.jpg"></p>
<p><strong>GET请求</strong></p>
<p><code>curl -XGET -H &#39;Content-type:application/json&#39; &#39;http://127.0.0.1:5001/api/_echo?a=1&amp;b=2&#39; -d &#39;{&quot;test&quot;: 1, &quot;USER&quot;: &quot;qqhc_clac_user&quot;}&#39; </code></p>
<p>请求报文</p>
<figure class="highlight nix"><table><tr><td class="code"><pre><span class="line">GET <span class="operator">/</span>api<span class="operator">/</span>_echo<span class="operator">?</span>a<span class="operator">=</span><span class="number">1</span>&amp;b<span class="operator">=</span><span class="number">2</span> HTTP<span class="symbol">/1.1</span></span><br><span class="line"><span class="params">User-Agent:</span> curl<span class="symbol">/7.19.7</span> (x86_64-redhat-linux-gnu) libcurl<span class="symbol">/7.19.7</span> NSS<span class="symbol">/3.13.1.0</span> zlib<span class="symbol">/1.2.7</span> libidn<span class="symbol">/1.18</span> libssh2<span class="symbol">/1.2.2</span></span><br><span class="line"><span class="params">Host:</span> <span class="number">127.0</span>.<span class="number">0.1</span>:<span class="number">5001</span></span><br><span class="line"><span class="params">Accept:</span> <span class="operator">*</span><span class="comment">/*</span></span><br><span class="line"><span class="comment">Content-type:application/json</span></span><br><span class="line"><span class="comment">Content-Length: 37</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">&#123;&quot;test&quot;: 1, &quot;USER&quot;: &quot;qqhc_clac_user&quot;&#125;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>第1行, 请求行</li>
<li>第2到6行, 请求头部(headers), key: value 格式</li>
<li>第7行, 空行, 分割headers 和 body</li>
<li>第8行, 请求数据, 也叫主体(body)</li>
</ul>
<p>注意: 这里的行与行之间的分隔符为 <code>\r\n</code></p>
<p><strong>POST请求</strong></p>
<p><code>curl -XPOST &#39;http://127.0.0.1:5001/api/_echo?a=1&amp;b=2&#39; -d &#39;c=3&amp;d=4&#39;  </code></p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">POST</span> /api/_echo?a=<span class="number">1</span>&amp;b=<span class="number">2</span> HTTP/<span class="number">1</span>.<span class="number">1</span></span><br><span class="line"><span class="attribute">User</span>-Agent: curl/<span class="number">7</span>.<span class="number">19</span>.<span class="number">7</span> (x86_64-redhat-linux-gnu) libcurl/<span class="number">7</span>.<span class="number">19</span>.<span class="number">7</span> NSS/<span class="number">3.13.1.0</span> zlib/<span class="number">1</span>.<span class="number">2</span>.<span class="number">7</span> libidn/<span class="number">1</span>.<span class="number">18</span> libssh2/<span class="number">1</span>.<span class="number">2</span>.<span class="number">2</span></span><br><span class="line"><span class="attribute">Host</span>: <span class="number">127.0.0.1:5001</span></span><br><span class="line"><span class="attribute">Accept</span>: */*</span><br><span class="line"><span class="attribute">Content</span>-Length: <span class="number">7</span></span><br><span class="line"><span class="attribute">Content</span>-Type: application/x-www-form-urlencoded</span><br><span class="line"></span><br><span class="line"><span class="attribute">c</span>=<span class="number">3</span>&amp;d=<span class="number">4</span></span><br></pre></td></tr></table></figure>


<h4 id="响应报文"><a href="#响应报文" class="headerlink" title="响应报文"></a>响应报文</h4><figure class="highlight http"><table><tr><td class="code"><pre><span class="line"><span class="meta">HTTP/1.1</span> <span class="number">200</span> OK</span><br><span class="line"><span class="attribute">Server</span><span class="punctuation">: </span>gunicorn/19.7.1</span><br><span class="line"><span class="attribute">Date</span><span class="punctuation">: </span>Tue, 26 Jun 2018 07:00:38 GMT</span><br><span class="line"><span class="attribute">Connection</span><span class="punctuation">: </span>keep-alive</span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>application/json</span><br><span class="line"><span class="attribute">Content-Length</span><span class="punctuation">: </span>45</span><br><span class="line"></span><br><span class="line"><span class="language-json"><span class="punctuation">&#123;</span></span></span><br><span class="line"><span class="language-json">  <span class="attr">&quot;USER&quot;</span><span class="punctuation">:</span> <span class="string">&quot;qqhc_clac_user&quot;</span><span class="punctuation">,</span></span></span><br><span class="line"><span class="language-json">  <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="number">1</span></span></span><br><span class="line"><span class="language-json"><span class="punctuation">&#125;</span></span></span><br></pre></td></tr></table></figure>

<ul>
<li>第1行, 状态行</li>
<li>第2到6行, 响应头部(headers), key: value 格式</li>
<li>第7行, 空行, 分割headers 和 body</li>
<li>剩下的其他行, 响应数据, 也叫主体(body)</li>
</ul>
<h3 id="响应状态码"><a href="#响应状态码" class="headerlink" title="响应状态码"></a>响应状态码</h3><p>HTTP之状态码</p>
<p>状态代码有三位数字组成，第一个数字定义了响应的类别，共分五种类别:</p>
<figure class="highlight ada"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span>xx：指示信息<span class="comment">--表示请求已接收，继续处理</span></span><br><span class="line"><span class="number">2</span>xx：成功<span class="comment">--表示请求已被成功接收、理解、接受</span></span><br><span class="line"><span class="number">3</span>xx：重定向<span class="comment">--要完成请求必须进行更进一步的操作</span></span><br><span class="line"><span class="number">4</span>xx：客户端错误<span class="comment">--请求有语法错误或请求无法实现</span></span><br><span class="line"><span class="number">5</span>xx：服务器端错误<span class="comment">--服务器未能实现合法的请求</span></span><br></pre></td></tr></table></figure>

<p>常见的状态码</p>
<ul>
<li>200 OK 客户端<strong>请求成功</strong></li>
<li>301 Moved Permanently 请求<strong>永久重定向</strong></li>
<li>302 Moved Temporarily 请求<strong>临时重定向</strong></li>
<li>304 Not Modified 文件未修改，可以直接使用缓存的文件。</li>
<li>400 Bad Request 由于客户端<strong>请求有语法错误</strong>，不能被服务器所理解。</li>
<li>401 Unauthorized <strong>请求未经授权</strong>。这个状态代码必须和WWW-Authenticate报头域一起使用</li>
<li>403 Forbidden 服务器收到请求，但是<strong>拒绝提供服务</strong>。服务器通常会在响应正文中给出不提供服务的原因</li>
<li>404 Not Found 请求的资源<strong>不存在</strong>，例如，输入了错误的URL</li>
<li>500 Internal Server Error 服务器发生不可预期的错误，导致无法完成客户端的请求。</li>
<li>503 Service Unavailable 服务器当前不能够处理客户端的请求，在一段时间之后，服务器可能会恢复正常。</li>
</ul>
<h3 id="HTTP请求方法"><a href="#HTTP请求方法" class="headerlink" title="HTTP请求方法"></a>HTTP请求方法</h3><p>HTTP1.1 版本</p>
<figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">GET</span>  请求指定的页面信息，并返回实体主体。</span><br><span class="line">HEAD     类似于<span class="keyword">get</span>请求，只不过返回的响应中没有具体的内容，用于获取报头</span><br><span class="line">POST     向指定资源提交数据进行处理请求（例如提交表单或者上传文件）。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。</span><br><span class="line">PUT  从客户端向服务器传送的数据取代指定的文档的内容。</span><br><span class="line"><span class="keyword">DELETE</span>   请求服务器删除指定的页面。</span><br><span class="line"><span class="keyword">CONNECT</span>  HTTP/<span class="number">1.1</span>协议中预留给能够将连接改为管道方式的代理服务器。</span><br><span class="line"><span class="keyword">OPTIONS</span>  允许客户端查看服务器的性能。</span><br><span class="line">TRACE    回显服务器收到的请求，主要用于测试或诊断。</span><br></pre></td></tr></table></figure>

<h2 id="Web的发展史"><a href="#Web的发展史" class="headerlink" title="Web的发展史"></a>Web的发展史</h2><h3 id="静态页面"><a href="#静态页面" class="headerlink" title="静态页面"></a>静态页面</h3><p>在很久很久以前, 那时Web开发还比较简单，开发者经常会去操作web服务器，并且他会写一些HTML页面放到服务器指定的文件夹(&#x2F;www)下。</p>
<p>这些HTML页面，就在浏览器请求页面时使用。<br><img data-src="https://image.ponder.work/mweb/2019-09-28-15300002589684.jpg"></p>
<p><strong>常见Web服务器</strong><br>最常见的Web服务器有Apache和Nginx, 也是静态资源服务器.</p>
<p>问题就出现了，你只能获取到静态内容。<br>倘若你想让访问者看到有多少其他访问者访问了这个网站呢，或者倘若你想让访问者去填写这样一个表单，包含有姓名和邮件地址呢？</p>
<p>所以出现动态技术. 静态资源服务器可以直接处理静态页面, 并且将动态请求转发给CGI程序处理。</p>
<h3 id="动态页面"><a href="#动态页面" class="headerlink" title="动态页面"></a>动态页面</h3><p><img data-src="https://image.ponder.work/mweb/2019-09-28-15300005473401.jpg"></p>
<h2 id="CGI"><a href="#CGI" class="headerlink" title="CGI"></a>CGI</h2><h3 id="什么是CGI"><a href="#什么是CGI" class="headerlink" title="什么是CGI"></a>什么是CGI</h3><p><code>通用网关接口</code>（Common Gateway Interface&#x2F;CGI）<br>CGI是动态网页的第一种解决方案. CGI协议描述了服务器和请求处理程序之间传输数据的一种标准。<br>遵循CGI协议的请求处理程序称为<code>CGI程序\脚本</code></p>
<h3 id="CGI协议数据流"><a href="#CGI协议数据流" class="headerlink" title="CGI协议数据流"></a>CGI协议数据流</h3><p><img data-src="https://image.ponder.work/mweb/2019-09-28-15300044921863.jpg"></p>
<p><img data-src="https://image.ponder.work/mweb/2019-09-28-%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_15300035568763.png" alt="企业微信截图_15300035568763"></p>
<h3 id="一个简单的CGI脚本"><a href="#一个简单的CGI脚本" class="headerlink" title="一个简单的CGI脚本"></a>一个简单的CGI脚本</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">#!C:\\Python27\\python.exe</span></span><br><span class="line"><span class="comment"># coding=utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cgi</span><br><span class="line">form = cgi.FieldStorage()</span><br><span class="line">text = form.getvalue(<span class="string">&#x27;text&#x27;</span>, <span class="built_in">open</span>(<span class="string">&#x27;simple_edit.dat&#x27;</span>).read())</span><br><span class="line">f = <span class="built_in">open</span>(<span class="string">&#x27;simple_edit.dat&#x27;</span>, <span class="string">&#x27;w&#x27;</span>)</span><br><span class="line">f.write(text)</span><br><span class="line">f.close()</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span> <span class="string">&#x27;Content-type: text/html\r\n&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span> <span class="string">&#x27;&#x27;&#x27;&lt;html&gt;</span></span><br><span class="line"><span class="string">  &lt;head&gt;</span></span><br><span class="line"><span class="string">    &lt;title&gt;A Simple Editor&lt;/title&gt;</span></span><br><span class="line"><span class="string">  &lt;/head&gt;</span></span><br><span class="line"><span class="string">  &lt;body&gt;</span></span><br><span class="line"><span class="string">    &lt;form action=&#x27;simple_edit.cgi&#x27; method=&#x27;POST&#x27;&gt;</span></span><br><span class="line"><span class="string">    &lt;textarea rows=&#x27;10&#x27; cols=&#x27;20&#x27; name=&#x27;text&#x27;&gt;%s&lt;/textarea&gt;&lt;br /&gt;</span></span><br><span class="line"><span class="string">    &lt;input type=&#x27;submit&#x27; /&gt;</span></span><br><span class="line"><span class="string">    &lt;/form&gt;</span></span><br><span class="line"><span class="string">  &lt;/body&gt;</span></span><br><span class="line"><span class="string">&lt;/html&gt;</span></span><br><span class="line"><span class="string">&#x27;&#x27;&#x27;</span> % text</span><br></pre></td></tr></table></figure>

<h3 id="CGI的发展"><a href="#CGI的发展" class="headerlink" title="CGI的发展"></a>CGI的发展</h3><h4 id="CGI局限性"><a href="#CGI局限性" class="headerlink" title="CGI局限性"></a>CGI局限性</h4><ul>
<li>伸缩性不是太好(经常是为每个请求分配一个新的进程)，</li>
<li>不太安全(直接使用文件系统或者环境变量)</li>
<li>功能十分有限：很难在CGI体系去对Web请求的控制，例如：用户认证等。</li>
</ul>
<p>每当客户请求CGI的时候，WEB服务器就请求操作系统生成一个新的CGI解释器进程(如php-cgi.exe)，CGI 的一个进程则处理完一个请求后退出，下一个请求来时再创建新进程。</p>
<p>当然，这样在访问量很少没有并发的情况也行。可是当访问量增大，并发存在，这种方式就不适合了。于是就有了fastcgi。</p>
<h4 id="FastCGI"><a href="#FastCGI" class="headerlink" title="FastCGI"></a>FastCGI</h4><p>FastCGI（快速通用网关接口），是CGI的增强版本。其目的在于，减少Web服务器与CGI程序之间交互的开销，使得服务器可以同时处理更多的请求。</p>
<p>FastCGI像是一个常驻(long-live)型的CGI，它可以一直执行着，只要激活后，不会每次都要花费时间去fork一次（这是CGI最为人诟病的fork-and-execute 模式）。</p>
<p>一般情况下，FastCGI的整个工作流程是这样的：</p>
<ol>
<li>Web Server启动时载入FastCGI进程管理器（IIS ISAPI或Apache Module)</li>
<li>FastCGI进程管理器自身初始化，启动多个CGI解释器进程(可见多个php-cgi)并等待来自Web Server的连接。</li>
<li>当客户端请求到达Web服务器时，Web服务器将请求通过socket方式转发到FastCGI主进程，主进程选择并连接到一个CGI解释器。Web服务器将CGI环境变量和标准输入发送到FastCGI子进程</li>
<li>FastCGI 子进程完成处理后将标准输出和错误信息从同一socket连接返回Web Server。当FastCGI子进程关闭连接时， 请求便告处理完成。</li>
<li>FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在Web Server中)的下一个连接。</li>
</ol>
<p><strong>FastCGI的PHP实现</strong></p>
<ul>
<li>PHP-FPM</li>
</ul>
<p><img data-src="https://image.ponder.work/mweb/2019-09-28-15300123292497.jpg"></p>
<h2 id="WSGI"><a href="#WSGI" class="headerlink" title="WSGI"></a>WSGI</h2><p>WSGI：全称是<code>Web Server Gateway Interface</code>，WSGI不是服务器，python模块，框架，API或者任何软件，只是一种<strong>规范</strong>，描述<code>web server</code>如何与<code>web application</code>通信的规范。</p>
<p>要实现WSGI协议，必须同时实现web server和web application，当前运行在WSGI协议之上的web框架有Torando, Flask, Django</p>
<p>WSGI server负责从客户端接收请求，将request转发给application，将application返回的response返回给客户端；</p>
<p>WSGI application接收由server转发的request，处理请求，并将处理结果返回给server。application中可以包括多个栈式的中间件(middlewares)，这些中间件需要同时实现server与application，因此可以在WSGI服务器与WSGI应用之间起调节作用：对服务器来说，中间件扮演应用程序，对应用程序来说，中间件扮演服务器。</p>
<p>WSGI协议其实是定义了一种server与application解耦的规范，即可以有多个实现WSGI server的服务器，也可以有多个实现WSGI application的框架，那么就可以选择任意的server和application组合实现自己的web应用。</p>
<p>例如uWSGI和Gunicorn都是实现了WSGI server协议的服务器，Django，Flask是实现了WSGI application协议的web框架，可以根据项目实际情况搭配使用。</p>
<p>WSGI将 web 组件分为三类： web服务器，web中间件,web应用程序， wsgi基本处理模式为</p>
<figure class="highlight xl"><table><tr><td class="code"><pre><span class="line">WSGI S<span class="function"><span class="title">erver</span> -&gt;</span> (WSGI M<span class="function"><span class="title">iddleware</span>)* -&gt;</span> WSGI Application</span><br></pre></td></tr></table></figure>

<h3 id="WSGI-Server"><a href="#WSGI-Server" class="headerlink" title="WSGI Server"></a>WSGI Server</h3><p>wsgi server可以理解为一个符合wsgi规范的web server，接收request请求，封装一系列环境变量，按照wsgi规范调用注册的wsgi app，最后将response返回给客户端。</p>
<p><img data-src="https://image.ponder.work/mweb/2019-09-28-15300098402454.png"></p>
<h3 id="WSGI-Application"><a href="#WSGI-Application" class="headerlink" title="WSGI Application"></a>WSGI Application</h3><p>wsgi application就是一个普通的callable对象，当有请求到来时，wsgi server会调用这个wsgi app。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">application</span> (environ, start_response):</span><br><span class="line"></span><br><span class="line">    response_body = <span class="string">&#x27;Request method: %s&#x27;</span> % environ[<span class="string">&#x27;REQUEST_METHOD&#x27;</span>]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># HTTP响应状态</span></span><br><span class="line">    status = <span class="string">&#x27;200 OK&#x27;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># HTTP响应头，注意格式</span></span><br><span class="line">    response_headers = [</span><br><span class="line">        (<span class="string">&#x27;Content-Type&#x27;</span>, <span class="string">&#x27;text/plain&#x27;</span>),</span><br><span class="line">        (<span class="string">&#x27;Content-Length&#x27;</span>, <span class="built_in">str</span>(<span class="built_in">len</span>(response_body)))</span><br><span class="line">    ]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 将响应状态和响应头交给WSGI server</span></span><br><span class="line">    start_response(status, response_headers)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 返回响应正文</span></span><br><span class="line">    <span class="keyword">return</span> [response_body]</span><br></pre></td></tr></table></figure>

<h3 id="WSGI-MiddleWare"><a href="#WSGI-MiddleWare" class="headerlink" title="WSGI MiddleWare"></a>WSGI MiddleWare</h3><p>有些功能可能介于服务器程序和应用程序之间，例如，服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理，这个功能叫做 URL Routing，这个功能就可以放在二者中间实现，这个中间层就是 middleware。</p>
<p>middleware对服务器程序和应用是透明的，也就是说，服务器程序以为它就是应用程序，而应用程序以为它就是服务器。</p>
<h2 id="Django简介"><a href="#Django简介" class="headerlink" title="Django简介"></a>Django简介</h2><p>Django是一个开放源代码的Web应用框架，由Python写成。采用了MVT的软件设计模式，即模型Model，视图View和模板Template。</p>
<h3 id="Django的MVT模型"><a href="#Django的MVT模型" class="headerlink" title="Django的MVT模型"></a>Django的MVT模型</h3><p><img data-src="https://image.ponder.work/mweb/2019-09-28-15300114452163.png"></p>
<h3 id="架构图"><a href="#架构图" class="headerlink" title="架构图"></a>架构图</h3><p><img data-src="https://image.ponder.work/mweb/2019-09-28-15300116078473.jpg"></p>
<h3 id="请求和响应"><a href="#请求和响应" class="headerlink" title="请求和响应"></a>请求和响应</h3><p><img data-src="https://image.ponder.work/mweb/2019-09-28-15300116231124.jpg"></p>
<h3 id="典型的目录结构"><a href="#典型的目录结构" class="headerlink" title="典型的目录结构"></a>典型的目录结构</h3><figure class="highlight vim"><table><tr><td class="code"><pre><span class="line">microapp</span><br><span class="line">├── manage.<span class="keyword">py</span>    # 程序启动入口</span><br><span class="line">├── microapp    # 项目配置目录</span><br><span class="line">│   ├── __init__.<span class="keyword">py</span></span><br><span class="line">│   ├── dev_settings.<span class="keyword">py</span></span><br><span class="line">│   ├── settings.<span class="keyword">py</span></span><br><span class="line">│   ├── urls.<span class="keyword">py</span></span><br><span class="line">│   └── wsgi.<span class="keyword">py</span></span><br><span class="line">├── translator   # web app, 可以有多个app</span><br><span class="line">    ├── __init__.<span class="keyword">py</span></span><br><span class="line">    ├── admin.<span class="keyword">py</span></span><br><span class="line">    ├── apps.<span class="keyword">py</span></span><br><span class="line">    ├── interfaces.<span class="keyword">py</span></span><br><span class="line">    ├── migrations</span><br><span class="line">    │   └── __init__.<span class="keyword">py</span></span><br><span class="line">    ├── models.<span class="keyword">py</span></span><br><span class="line">    ├── tests.<span class="keyword">py</span></span><br><span class="line">    ├── urls.<span class="keyword">py</span></span><br><span class="line">    └── views.<span class="keyword">py</span></span><br></pre></td></tr></table></figure>

<h3 id="中间件"><a href="#中间件" class="headerlink" title="中间件"></a>中间件</h3><p>中间件的执行流程 (Django1.9 以后的版本)</p>
<p>1、执行完所有的request方法 到达视图函数。</p>
<p>2、执行中间件的其他方法</p>
<p>3、经过所有response方法 返回客户端。</p>
<p>注意：如果在其中1个中间件里 request方法里 return了值，就会执行当前中间件的response方法，返回给用户 然后 报错。。不会再执行下一个中间件。</p>
<p><img data-src="https://image.ponder.work/mweb/2019-09-28-15308821801095.jpg"></p>
<p>一个简单的中间件</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;获取真实用户ip&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.utils.deprecation <span class="keyword">import</span> MiddlewareMixin</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RemoteHostMiddleware</span>(<span class="title class_ inherited__">MiddlewareMixin</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">process_view</span>(<span class="params">self, request, view, args, kwargs</span>):</span><br><span class="line">        m = request.META</span><br><span class="line">        <span class="keyword">if</span> m.get(<span class="string">&quot;HTTP_X_REAL_IP&quot;</span>):</span><br><span class="line">            ip = m.get(<span class="string">&quot;HTTP_X_REAL_IP&quot;</span>)</span><br><span class="line">        <span class="keyword">elif</span> m.get(<span class="string">&#x27;HTTP_X_FORWARDED_FOR&#x27;</span>):</span><br><span class="line">            x_forwarded_for = m.get(<span class="string">&#x27;HTTP_X_FORWARDED_FOR&#x27;</span>)</span><br><span class="line">            ip = x_forwarded_for.split(<span class="string">&#x27;,&#x27;</span>)[<span class="number">0</span>]</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            ip = m.get(<span class="string">&#x27;REMOTE_ADDR&#x27;</span>)</span><br><span class="line">        request.remote_host = ip</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol>
<li><a href="https://hit-alibaba.github.io/interview/basic/network/HTTP.html">https://hit-alibaba.github.io/interview/basic/network/HTTP.html</a></li>
<li><a href="https://code.ziqiangxuetang.com/django/django-middleware.html">https://code.ziqiangxuetang.com/django/django-middleware.html</a></li>
<li><a href="http://blog.jobbole.com/45170/">http://blog.jobbole.com/45170/</a></li>
<li><a href="https://www.cnblogs.com/wanghetao/p/3934350.html">https://www.cnblogs.com/wanghetao/p/3934350.html</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>Django</tag>
        <tag>Web</tag>
      </tags>
  </entry>
  <entry>
    <title>Python中yield语句的运行机制</title>
    <url>/2016/04/16/python%E4%B8%ADyield%E8%AF%AD%E5%8F%A5%E7%9A%84%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>如果一个在函数中存在yield关键字，那么这个函数就构成了生成器。生成器是一个函数，它生成一个序列，以便在迭代中使用。<br>调用这个函数，并不会马上开始执行函数体中的代码，而是返回一个生成器对象，通过调用生成器对象的<code>next()</code>方法（python3中是<code>__netx__()</code>）执行函数。</p>
<p>那么，具体的函数中语句的执行顺序是怎么样的呢？</p>
<h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">func</span>(<span class="params">x=<span class="number">10</span></span>):</span><br><span class="line">    <span class="built_in">print</span> <span class="string">&#x27;the beginning of function&#x27;</span></span><br><span class="line">    <span class="keyword">if</span> x &lt;= <span class="number">0</span> <span class="keyword">or</span> <span class="keyword">not</span> <span class="built_in">isinstance</span>(x, <span class="built_in">int</span>):</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(x):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;before yield&#x27;</span>, i</span><br><span class="line">        <span class="keyword">yield</span> i</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;after yield&#x27;</span>, i</span><br><span class="line"></span><br><span class="line">gen = func(<span class="number">2</span>)</span><br><span class="line"><span class="built_in">print</span> <span class="string">&#x27;*&#x27;</span>*<span class="number">20</span></span><br><span class="line"><span class="built_in">print</span> <span class="string">&#x27;-&gt; yielding: %s&#x27;</span> % gen.<span class="built_in">next</span>()</span><br><span class="line"><span class="built_in">print</span> <span class="string">&#x27;*&#x27;</span>*<span class="number">20</span></span><br><span class="line"><span class="built_in">print</span> <span class="string">&#x27;-&gt; yielding: %s&#x27;</span> % gen.<span class="built_in">next</span>()</span><br><span class="line"><span class="built_in">print</span> <span class="string">&#x27;*&#x27;</span>*<span class="number">20</span></span><br><span class="line"><span class="built_in">print</span> <span class="string">&#x27;-&gt; yielding: %s&#x27;</span> % gen.<span class="built_in">next</span>()</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line"><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span></span><br><span class="line">the beginning of function</span><br><span class="line">before yield 0</span><br><span class="line">-&gt; yielding: 0</span><br><span class="line"><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span></span><br><span class="line">after yield 0</span><br><span class="line">before yield 1</span><br><span class="line">-&gt; yielding: 1</span><br><span class="line"><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span></span><br><span class="line">after yield 1</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File &quot;D:\Projects\test.py&quot;, line 28, in <span class="language-xml"><span class="tag">&lt;<span class="name">module</span>&gt;</span></span></span><br><span class="line"><span class="code">    print &#x27;-&gt; yielding: %s&#x27; % gen.next()</span></span><br><span class="line"><span class="code">StopIteration</span></span><br></pre></td></tr></table></figure>

<h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><ol>
<li>首先运行<code>func(2)</code>，这时函数的所有语句都没有执行，返回一个生成器赋值给<code>gen</code>。</li>
<li>第一次执行<code>gen.next()</code>，函数从头开始执行，运行完yield语句暂停住了。</li>
<li>再次运行<code>gen.next()</code>，从停下的地方继续，直到遇到遇到下一个yield，运行完yield语句又暂停住了。</li>
<li>第三次尝试运行<code>gen.next()</code>，运行完<code>print &#39;after yield&#39;, i</code>，由于循环次数已满，找不到下一个yield，就出现<code>StopIteration</code>错误</li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python中的装饰器（decorator）</title>
    <url>/2016/04/27/python%E4%B8%AD%E7%9A%84%E8%A3%85%E9%A5%B0%E5%99%A8/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>顾名思义，所谓装饰器就是对原有的对象做一些装饰，也就是给已有的对象添加一些功能。</p>
<p>假如我现在想在函数运行时输出一些信息</p>
<h2 id="小学水平"><a href="#小学水平" class="headerlink" title="小学水平"></a>小学水平</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">func</span>):</span><br><span class="line">    <span class="built_in">print</span> <span class="string">&#x27;`%s` is running...&#x27;</span> % func.__name__</span><br><span class="line">    <span class="keyword">return</span> func</span><br><span class="line">    </span><br><span class="line"><span class="meta">@running</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">my_sum</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;I cat&#x27;t sum right now!&quot;</span></span><br><span class="line">    </span><br><span class="line"><span class="built_in">print</span> my_sum()</span><br></pre></td></tr></table></figure>
<p>输出</p>
<figure class="highlight excel"><table><tr><td class="code"><pre><span class="line">`my_sum` is running...</span><br><span class="line">I cat&#x27;<span class="built_in">t</span> <span class="built_in">sum</span> <span class="built_in">right</span> <span class="built_in">now</span>!</span><br></pre></td></tr></table></figure>
<p>要使用装饰器，先得定义一个装饰器函数，然后在需要装饰的函数的前一行使用<code>@</code>符号加上<code>装饰器名称</code>。<br>在这里的效果等效于running(my_sum)()，不过看起来有点别扭。<br><strong>注意：一旦通过<code>@running</code>装饰了函数，不管被装饰函数是否运行，python解释器都会执行一遍<code>running</code>函数。</strong></p>
<h2 id="中学水平"><a href="#中学水平" class="headerlink" title="中学水平"></a>中学水平</h2><p>上一个装饰器用起来还行，但是有一个致命的问题，它不能装饰带参数的函数。所以我们在装饰器内部定义<code>_wrapper</code>函数，并返回它。这个函数接收所有位置参数<code>*args</code>，和关键字参数<code>*kwargs</code>，在<code>_wrapper</code>内部执行<code>func(*args, **kwargs)</code>。</p>
<span id="more"></span>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">func</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;`%s` is running...&#x27;</span> % func.__name__</span><br><span class="line">        <span class="keyword">return</span> func(*args, **kwargs)</span><br><span class="line">    <span class="keyword">return</span> _wrapper</span><br><span class="line">    </span><br><span class="line"><span class="meta">@running</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">my_sum</span>(<span class="params">a, b=<span class="number">2</span></span>):</span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span> my_sum(<span class="number">1</span>,<span class="number">2</span>)</span><br><span class="line"><span class="built_in">print</span> my_sum.__name__</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line">`my_sum` <span class="keyword">is</span> <span class="built_in">running</span>...</span><br><span class="line"><span class="number">3</span></span><br><span class="line">_wrapper</span><br></pre></td></tr></table></figure>

<p>这也是python中最普通的装饰器，假如需要在<code>my_sum</code>运行之后添加一些功能，则可以改成这样。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">func</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        start = time.time()</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;`%s` is running...&#x27;</span> % func.__name__</span><br><span class="line">        _result = func(*args, **kwargs)</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;run `%s` takes %s seconds&#x27;</span> % (func.__name__, time.time()-start)</span><br><span class="line">        <span class="keyword">return</span> _result</span><br><span class="line">    <span class="keyword">return</span> _wrapper</span><br></pre></td></tr></table></figure>

<h2 id="大学水平"><a href="#大学水平" class="headerlink" title="大学水平"></a>大学水平</h2><p>上一个装饰器也有一个问题，就是经过装饰的<code>my_sum.__name__</code>变成了<code>_wrapper</code>。<br>这个问题可以通过python内置的<code>functools.wraps</code>解决，这个装饰器对原函数的一些属性进行了复制。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> functools</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">running</span>(<span class="params">func</span>):</span><br><span class="line"><span class="meta">    @functools.wraps(<span class="params">func</span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_wrapper</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line">        start = time.time()</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;`%s` is running...&#x27;</span> % func.__name__</span><br><span class="line">        _result = func(*args, **kwargs)</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&#x27;run `%s` takes %s seconds&#x27;</span> % (func.__name__, time.time()-start)</span><br><span class="line">        <span class="keyword">return</span> _result</span><br><span class="line">    <span class="keyword">return</span> _wrapper</span><br><span class="line"></span><br><span class="line"><span class="meta">@running</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">my_sum</span>(<span class="params">a=<span class="number">1</span>, b=<span class="number">2</span></span>):</span><br><span class="line">    time.sleep(<span class="number">1</span>)</span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span> my_sum(<span class="number">1</span>,<span class="number">2</span>)</span><br><span class="line"><span class="built_in">print</span> my_sum.__name__</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight autohotkey"><table><tr><td class="code"><pre><span class="line">`my_sum` is running...</span><br><span class="line">run `my_sum` takes <span class="number">1.0</span> seconds</span><br><span class="line"><span class="number">3</span></span><br><span class="line">my_sum</span><br></pre></td></tr></table></figure>




]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>python包导入再谈</title>
    <url>/2016/06/22/python%E5%8C%85%E5%AF%BC%E5%85%A5%E5%86%8D%E8%B0%88/</url>
    <content><![CDATA[<h2 id="python包导入机制"><a href="#python包导入机制" class="headerlink" title="python包导入机制"></a>python包导入机制</h2><p>包是指含有<code>__init__.py</code>的文件夹，模块就是一个.py文件。</p>
<p><strong>python的包和模块是先查找<code>buil-in moudle</code>然后是<code>sys.path</code>这个list里的所有路径。</strong></p>
<ul>
<li>sys.path[0]是启动Python解释器的脚本所在路径<ul>
<li>比如运行<code>python task/admin.py</code>，sys.path[0]是<code>&#39;/home/code/taskman/task&#39;</code></li>
<li>启动脚本是符号链接时，<code>sys.path[0]</code>是实际文件所在路径，<a href="#%E7%AC%A6%E5%8F%B7%E9%93%BE%E6%8E%A5%E7%9A%84%E6%83%85%E5%86%B5">详见</a></li>
<li>如果启动的是python解释器，如idle,ipython等，sys.path[0]是<code>&#39;&#39;</code>。</li>
</ul>
</li>
<li>如果sys.path[0]是<code>&#39;&#39;</code>，这个值等于当前工作目录也就是<code>os.getcwd()</code>的值，也就是linux的pwd的值，你可以用<code>os.chdir</code>来改变工作路径。</li>
<li>sys.path里余下的元素是python环境变量里的一些路径</li>
</ul>
<p>还是分析之前的那个例子</p>
<span id="more"></span>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">taskman</span><br><span class="line">    ├── db<span class="selector-class">.sqlite3</span></span><br><span class="line">    ├── manage<span class="selector-class">.py</span></span><br><span class="line">    ├── requirement<span class="selector-class">.txt</span></span><br><span class="line">    ├── task</span><br><span class="line">    │   ├── __init__<span class="selector-class">.py</span></span><br><span class="line">    │   ├── admin<span class="selector-class">.py</span></span><br><span class="line">    │   ├── models<span class="selector-class">.py</span></span><br><span class="line">    │   ├── urls<span class="selector-class">.py</span></span><br><span class="line">    │   └── views<span class="selector-class">.py</span></span><br><span class="line">    └── man</span><br><span class="line">        ├── __init__<span class="selector-class">.py</span></span><br><span class="line">        ├── settings<span class="selector-class">.py</span></span><br><span class="line">        ├── urls<span class="selector-class">.py</span></span><br><span class="line">        └── wsgi.py</span><br></pre></td></tr></table></figure>


<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># task/admin.py 文件</span></span><br><span class="line"><span class="keyword">from</span> man.setting <span class="keyword">import</span> some_setting</span><br><span class="line"></span><br><span class="line">some_setting()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    <span class="built_in">print</span> <span class="string">&#x27;something&#x27;</span></span><br></pre></td></tr></table></figure>

<h2 id="工作路径的影响"><a href="#工作路径的影响" class="headerlink" title="工作路径的影响"></a>工作路径的影响</h2><h3 id="当前路径是-taskman"><a href="#当前路径是-taskman" class="headerlink" title="当前路径是 taskman"></a>当前路径是 <code>taskman</code></h3><p>假如我们要直接运行<code>task/admin.py</code>这个文件，直接<code>python task/admin.py</code>，会抛出<code>ImportError</code>。</p>
<p>直接<code>python task/admin.py</code>，此时的sys.path[0]应该是<code>&#39;/home/code/taskman/task&#39;</code>，此时task&#x2F;admin.py所需的man.setting显然不在<code>task</code>目录下，自然就是<code>ImportError</code>。<br>这时如果往admin.py加入<code>sys.path.append(&#39;/home/code/taskman&#39;)</code>语句，就可以把<code>man</code>这个包所在目录加入到sys.path，这样就解决了。</p>
<p>也可以使用<code>python -m task.admin</code> 将admin.py当作一个模块来执行，此时sys.path[0]是当前工作目录<code>/home/code/taskman</code>，所以一切正常。</p>
<h3 id="当前路径不是-taskman"><a href="#当前路径不是-taskman" class="headerlink" title="当前路径不是 taskman"></a>当前路径不是 <code>taskman</code></h3><p>运行<code>python -m task.admin</code>会提示<code>No module named task</code>，因为在那个目录下找不到<code>task</code>这个包。</p>
<p>此时只能在脚本前部添加一下语句</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> os, sys</span><br><span class="line">os.chdir(<span class="string">&#x27;/home/code/taskman/task&#x27;</span>)`</span><br><span class="line">sys.path.append(<span class="string">&#x27;/home/code/taskman&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>这样在这个脚本的包导入正常，同时还可以通过<code>os.system(&#39;python -m task.models&#39;)</code>运行项目内其他模块，而不用管当前工作目录在那里。</p>
<p>还有一个霸道的解决方法是，通过<code>export PYTHONPATH=&quot;/home/code/taskman&quot;</code>将项目路径，添加到python环境变量里，这样当解释器启动时项目路径就会直接添加到sys.path中。不过这样会影响整个python环境，可能出现包重名。</p>
<p>当然还可以在Python的site-packages目录下新建一个<code>some.pth</code>文件，把需要的路径往里面写，这样每一个Python解释器进程的sys.path都会包含这个目录。可能会影响其他用户的程序，不推荐使用。</p>
<h2 id="符号链接的情况"><a href="#符号链接的情况" class="headerlink" title="符号链接的情况"></a>符号链接的情况</h2><p>当Python程序的入口文件是符号链接时<code>sys.path[0]</code>是实际文件所在路径，所以可能会出现找不到模块的情况。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">➜ tree</span><br><span class="line">.</span><br><span class="line">├── aa.py -&gt; abc/aa.py</span><br><span class="line">├── abc</span><br><span class="line">│   └── aa.py</span><br><span class="line">└── b.py</span><br><span class="line"></span><br><span class="line">1 directory, 3 files</span><br><span class="line"></span><br><span class="line">➜ <span class="built_in">cat</span> abc/aa.py</span><br><span class="line">import sys</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(sys.path)</span><br><span class="line">abc=1</span><br><span class="line">from b import *</span><br><span class="line"></span><br><span class="line">➜ <span class="built_in">cat</span> b.py</span><br><span class="line">b=2</span><br><span class="line"></span><br><span class="line">➜ python aa.py</span><br><span class="line">[<span class="string">&#x27;/private/tmp/tmp1/abc&#x27;</span>, </span><br><span class="line"><span class="string">&#x27;/Users/ruan/.pyenv/versions/3.8.7/lib/python38.zip&#x27;</span>, </span><br><span class="line"><span class="string">&#x27;/Users/ruan/.pyenv/versions/3.8.7/lib/python3.8&#x27;</span>, </span><br><span class="line"><span class="string">&#x27;/Users/ruan/.pyenv/versions/3.8.7/lib/python3.8/lib-dynload&#x27;</span>, </span><br><span class="line"><span class="string">&#x27;/Users/ruan/.pyenv/versions/3.8.7/lib/python3.8/site-packages&#x27;</span>]</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;aa.py&quot;</span>, line 5, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    from b import *</span><br><span class="line">ModuleNotFoundError: No module named <span class="string">&#x27;b&#x27;</span></span><br></pre></td></tr></table></figure>

<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol>
<li>Python模块和包的搜索路径由<code>sys.path</code>列表决定</li>
<li>Python会将启动脚本所在路径加入到<code>sys.path[0]</code>, 也就是<code>python /home/a/b/c.py</code>的<code>sys.path[0]</code>是<code>/home/a/b/</code></li>
<li>Python执行加<code>-m</code>参数，会将工作路径加入到<code>sys.path[0]</code>, 也就是 <code>python -m a.b.c</code>的<code>sys.path[0]</code>是<code>/home</code></li>
<li>环境变量<code>PYTHONPATH</code>，<code>some.pth</code>文件会影响<code>sys.path</code>列表</li>
<li>sys.path中，空路径<code>&#39;&#39;</code>总是等效于当前工作路径, 也就是<code>os.chdir</code>改变而改变。</li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>python赋值语句小坑</title>
    <url>/2016/01/10/python%E8%B5%8B%E5%80%BC%E8%AF%AD%E5%8F%A5%E5%B0%8F%E5%9D%91/</url>
    <content><![CDATA[<p>说到python赋值语句，大家想必一个念头——so easy，不是<code>lz = &#39;dashabi&#39;</code>吗。<br>确实，easy是easy，里面却有个小坑，虽小，却不易发现。在代码量多的时候，就有点恶心了。</p>
<p><strong>正常的语句是这样</strong></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>aa = <span class="string">u&#x27;这是一个坑&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>aa </span><br><span class="line"><span class="string">u&#x27;\u8fd9\u662f\u4e00\u4e2a\u5751&#x27;</span></span><br></pre></td></tr></table></figure>

<p><strong>坑在这里</strong></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>aa = <span class="string">u&#x27;这是一个坑&#x27;</span>,</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>aa </span><br><span class="line">(<span class="string">u&#x27;\u8fd9\u662f\u4e00\u4e2a\u5751&#x27;</span>,)</span><br></pre></td></tr></table></figure>
<p>看出区别没有，坑的后面有个逗号，平时这逗号没什么卵用，但在赋值语句的末尾会将原来的对象转化为tuple。<br>和<code>aa = (u&#39;这是一个坑&#39;,)</code>是一样的效果。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>Python asyncio 简易教程</title>
    <url>/2020/05/24/python-asyncio-usage/</url>
    <content><![CDATA[<p><code>asyncio</code>是Python 3.4版本引入的标准库，直接内置了对异步IO的支持。</p>
<p><code>asyncio</code>的编程模型就是一个消息循环。我们从<code>asyncio</code>模块中直接获取一个<code>EventLoop</code>的引用，然后把需要执行的协程扔到<code>EventLoop</code>中执行，就实现了异步IO。</p>
<span id="more"></span>

<h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">hello_world</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">    <span class="keyword">await</span> asyncio.sleep(<span class="number">1</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27; world&#x27;</span>)</span><br><span class="line"></span><br><span class="line">loop = asyncio.get_event_loop()</span><br><span class="line">loop.run_until_complete(hello_world())</span><br><span class="line">loop.close()</span><br></pre></td></tr></table></figure>

<h2 id="执行逻辑"><a href="#执行逻辑" class="headerlink" title="执行逻辑"></a>执行逻辑</h2><p><code>async def</code> 定义的每一个函数, 本质上是一个协程.<br>当await调用一个函数时, 程序就切换到该函数中执行了, 当执行完成时, 程序又回到await调用处继续执行.</p>
<p>当函数的调用链上碰到系统io相关函数时，程序执行的控制权就会回到eventloop主循环，eventloop就会调度执行别的函数，等到该函数的io就绪时，再从该函数暂停的地方继续执行。</p>
<p>再搭配上支持非阻塞io的异步库, 这样就实现了高效的异步编程.</p>
<p>所有异步函数是在同一个线程中执行的, 在该进程内我们还可启用其他线程, 执行其他同步代码. 下图展示了Python中协程、线程、进程的逻辑关系。</p>
<p><img data-src="https://image.ponder.work/mweb/2020-05-24-15903277834535.jpg"></p>
<h2 id="如何在event-loop内调用同步代码"><a href="#如何在event-loop内调用同步代码" class="headerlink" title="如何在event loop内调用同步代码"></a>如何在event loop内调用同步代码</h2><p>一旦使用async, 整个线程内都必须使用异步, 否则整个线程都会阻塞。<br>所以，必须另起线程, 将同步操作放在线程中执行。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> concurrent.futures</span><br><span class="line"></span><br><span class="line">executor = concurrent.futures.ThreadPoolExecutor(</span><br><span class="line">    max_workers=<span class="number">2</span>,  <span class="comment"># 线程池大小</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">some_block_things</span>(<span class="params">x</span>):</span><br><span class="line">    time.sleep(<span class="number">1</span>)</span><br><span class="line">    <span class="keyword">return</span> x</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">hello_world</span>(<span class="params">x</span>):</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;hello world&#x27;</span>, time.time())</span><br><span class="line">    loop = asyncio.get_event_loop()</span><br><span class="line">    fut = loop.run_in_executor(executor, some_block_things, x)</span><br><span class="line">    num =  <span class="keyword">await</span> asyncio.wait_for(fut, timeout=<span class="number">10</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;num %s&#x27;</span> % num)</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    start = time.time()</span><br><span class="line">    <span class="keyword">await</span> asyncio.wait([hello_world(i) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">8</span>)])</span><br><span class="line">    end = time.time()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Complete in &#123;&#125; seconds&quot;</span>.<span class="built_in">format</span>(end - start))</span><br><span class="line"></span><br><span class="line">loop = asyncio.get_event_loop()</span><br><span class="line">loop.run_until_complete(main())</span><br><span class="line">loop.close()</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">hello</span> world <span class="number">1533904346</span>.<span class="number">37133</span></span><br><span class="line"><span class="attribute">hello</span> world <span class="number">1533904346</span>.<span class="number">371683</span></span><br><span class="line"><span class="attribute">hello</span> world <span class="number">1533904346</span>.<span class="number">37199</span></span><br><span class="line"><span class="attribute">hello</span> world <span class="number">1533904346</span>.<span class="number">372059</span></span><br><span class="line"><span class="attribute">hello</span> world <span class="number">1533904346</span>.<span class="number">372122</span></span><br><span class="line"><span class="attribute">hello</span> world <span class="number">1533904346</span>.<span class="number">372176</span></span><br><span class="line"><span class="attribute">hello</span> world <span class="number">1533904346</span>.<span class="number">372245</span></span><br><span class="line"><span class="attribute">hello</span> world <span class="number">1533904346</span>.<span class="number">372295</span></span><br><span class="line"><span class="attribute">num</span> <span class="number">0</span></span><br><span class="line"><span class="attribute">num</span> <span class="number">3</span></span><br><span class="line"><span class="attribute">num</span> <span class="number">4</span></span><br><span class="line"><span class="attribute">num</span> <span class="number">1</span></span><br><span class="line"><span class="attribute">num</span> <span class="number">5</span></span><br><span class="line"><span class="attribute">num</span> <span class="number">6</span></span><br><span class="line"><span class="attribute">num</span> <span class="number">7</span></span><br><span class="line"><span class="attribute">num</span> <span class="number">2</span></span><br><span class="line"><span class="attribute">Complete</span> in <span class="number">4</span>.<span class="number">015289068222046</span> seconds<span class="meta"></span></span><br><span class="line"><span class="meta">[Finished in 4.2s]</span></span><br></pre></td></tr></table></figure>

<p>因为这里我的线程池大小为2, 所以8个sleep整体耗时4s, 但是’hello world’ 几乎是同一时刻输出的, 这就是asyncio的魅力所在.</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>asyncio使用<code>async</code>和<code>await</code>的语法，使得原来回调函数形式的异步代码变为同步代码，更易于理解。<br>使用asyncio可以方便地构建高性能的网络服务, 单进程qps可以轻松地达到2000以上。<br>基于asyncio也出现了一些异步web框架，比如sanic，相比传统框架性能提升较大。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>异步</tag>
      </tags>
  </entry>
  <entry>
    <title>Redis主从复制机制</title>
    <url>/2021/07/21/redis-master-slave/</url>
    <content><![CDATA[<p>Redis作为内存型数据，为了高可用，必须有数据备份，这里采取主从的模式。<br>用户可以通过执行 SLAVEOF 命令或者设置 slaveof 选项，让一个服务器去复制 （replicate) 另一个服务器。</p>
<span id="more"></span>
<p>如果服务器 <code>127.0.0.1:12345</code> 向 <code>127.0.0.1:6379</code> 发送 <code>SLAVEOF 127.0.0.1 6379</code>, 则服务器（12345）将成为服务器（6379）的从服务器。</p>
<h2 id="旧版复制功能（全量复制）"><a href="#旧版复制功能（全量复制）" class="headerlink" title="旧版复制功能（全量复制）"></a>旧版复制功能（全量复制）</h2><p>Redis的复制功能分为同步（RDB文件）和命令传播（同步写命令）两个阶段，具体步骤如下。</p>
<ol>
<li>从服务器发送<code>SYNC</code></li>
<li>主服务器接收<code>SYNC</code>后执行<code>BGSAVE</code>生成RDB文件，同时用缓冲区记录之后所有写命令。</li>
<li>主服务器发送RDB到从服务器，从服务器加载RDB</li>
<li>主服务器发送缓冲区内的写命令</li>
<li>之后主服务器写命令都需要同时发一份给从服务器（命令传播）</li>
</ol>
<p><img data-src="https://image.ponder.work/mweb/2021-07-25-16272051223635.jpg" alt="-w908"></p>
<h2 id="缺陷及解决方案"><a href="#缺陷及解决方案" class="headerlink" title="缺陷及解决方案"></a>缺陷及解决方案</h2><p>由于RDB的生成发送非常耗时，主从短暂断线的情况下，也需要重复生成，主从同步的效率就非常低了。</p>
<p>Redis从2.8版本开始, 使用<code>PSYNC</code>命令替换<code>SYNC</code>，增加了部分同步功能，对断线重连的情况进行了优化。</p>
<p>主从服务器都记录了<strong>复制偏移量</strong>，记录了主服务器发出的字节数和从服务器收到的字节数,并且主服务器使用一个<strong>复制积压缓冲区</strong>记录最近发出的数据（FIFO）。</p>
<p>同步时，从服务器会发送复制偏移量。</p>
<ul>
<li>如果，主从偏移量相差的这部分数据在缓冲区中，则主服务器就发送这部分数据</li>
<li>否则，执行全量复制。</li>
</ul>
<p>同时，主服务器每次启动时会生成<strong>运行id</strong>，防止主服务器重启后复制混乱。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-07-25-16272066103593.jpg" alt="-w586"><br><img data-src="https://image.ponder.work/mweb/2021-07-25-16272066397939.jpg" alt="-w552"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>redis 3.0 源码</li>
<li>redis 设计与实现</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Redis</tag>
      </tags>
  </entry>
  <entry>
    <title>树莓派系统备份</title>
    <url>/2020/10/31/raspi-system-backup/</url>
    <content><![CDATA[<p>在玩树莓派的过程中难免会碰到如何高效的备份系统的问题。</p>
<p>由于树莓派用的是Linux系统，所以常见的有两种备份方式</p>
<ol>
<li>基于文件的备份，比如tar，rsync</li>
<li>基于磁盘的备份，比如dd</li>
</ol>
<p>这两种备份方式各有利弊：<br>基于文件的备份占用空间小，而且可以在系统在线时操作，比较方便，但是当要还原整个系统时就会比较麻烦（引导重建等等）。<br>基于磁盘的备份就比较简单粗暴了，直接克隆硬盘，恢复时直接还原映像文件就好了，但是由于是整盘备份，空间占用比较大。</p>
<span id="more"></span>

<p>我这里是采用的第二种备份方式，通过缩减分区大小，排除未使用空间来减小备份文件大小</p>
<h2 id="具体步骤"><a href="#具体步骤" class="headerlink" title="具体步骤"></a>具体步骤</h2><p>首先需要将树莓派的SD取下，插入到一台Linux机器上。</p>
<h3 id="使用Gparted缩小分区"><a href="#使用Gparted缩小分区" class="headerlink" title="使用Gparted缩小分区"></a>使用Gparted缩小分区</h3><p>这一步操作比较耗时，跟SD卡大小和速度有关，基本在几分钟到几十分钟不等。</p>
<p><img data-src="https://image.ponder.work/mweb/2020-11-01-16041166087052.jpg" alt="-w761"><br><img data-src="https://image.ponder.work/mweb/2020-11-01-16041166419441.jpg" alt="-w764"><br><img data-src="https://image.ponder.work/mweb/2020-11-01-16041167175534.jpg" alt="-w760"><br><img data-src="https://image.ponder.work/mweb/2020-11-01-16041167579651.jpg" alt="-w756"><br><img data-src="https://image.ponder.work/mweb/2020-11-01-16041167920279.jpg" alt="-w764"><br><img data-src="https://image.ponder.work/mweb/2020-11-01-16041169311741.jpg" alt="-w708"></p>
<h3 id="挂载网络存储"><a href="#挂载网络存储" class="headerlink" title="挂载网络存储"></a>挂载网络存储</h3><p>如果不想把备份文件存到网络存储上，该步骤可以忽略</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo  mount.cifs -o vers=2.0,user=$&#123;nas_user&#125;,password=$&#123;nas_password&#125;,uid=$(id -u),gid=$(id -g) \</span><br><span class="line">	//192.168.123.100/D-soft/ /mnt/</span><br></pre></td></tr></table></figure>

<h3 id="使用DD备份硬盘"><a href="#使用DD备份硬盘" class="headerlink" title="使用DD备份硬盘"></a>使用DD备份硬盘</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo dd if=/dev/sda of=/mnt/backup.img bs=1M count=6000 status=progress</span><br></pre></td></tr></table></figure>

<p>备份耗时跟SD卡大小和速度有关，基本在几分钟到几十分钟不等。</p>
<p>bs参数代表备份文件大小的单位，这里是1M</p>
<p>count代表有多少bs，也就是备份大小是<code>count*bs=6000M</code><br>这个数值需要根据你磁盘使用空间来计算，取一个大于已使用空间的值就好了。</p>
<p>例如，我这个SD卡已使用的空间是 <code>4M + 256M + 5.24GiB = 5625.76M</code>，则备份大小取6000M</p>
<h3 id="还原系统"><a href="#还原系统" class="headerlink" title="还原系统"></a>还原系统</h3><p>还原系统和新安装系统是一样的，用官方的<a href="https://www.raspberrypi.org/downloads/">Raspberry Pi Imager</a>还原备份镜像文件即可</p>
<p>还原完成之后，还需要使用Gparted将缩小的分区还原到原来的大小，耗时大概十几秒。</p>
<p>然后插卡开机即可</p>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>Linux</tag>
        <tag>树莓派</tag>
      </tags>
  </entry>
  <entry>
    <title>Redis原理 —— dict 字典</title>
    <url>/2021/04/17/redis-source-code-data-structure-dict/</url>
    <content><![CDATA[<p>这是Redis源码阅读系列第一篇文章。</p>
<p>dict 是 redis 最重要的数据结构，db、hash、以及服务器内部需要用到hashmap的场景都是用dict来实现的。学习 dict 的源码，我们可以学到hashmap的原理及实现。</p>
<span id="more"></span>

<h2 id="dict-数据结构"><a href="#dict-数据结构" class="headerlink" title="dict 数据结构"></a>dict 数据结构</h2><p>哈希表元素节点</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dictEntry</span> &#123;</span></span><br><span class="line">    <span class="comment">// 键，指向SDS(Redis字符串实现)</span></span><br><span class="line">    <span class="type">void</span> *key;</span><br><span class="line">    <span class="comment">// 值, 联合值, 可以是整数或者指针</span></span><br><span class="line">    <span class="class"><span class="keyword">union</span> &#123;</span></span><br><span class="line">        <span class="type">void</span> *val;</span><br><span class="line">        <span class="type">uint64_t</span> u64;</span><br><span class="line">        <span class="type">int64_t</span> s64;</span><br><span class="line">    &#125; v;</span><br><span class="line">    <span class="comment">// 指向下个哈希表节点，形成链表</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">dictEntry</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125; dictEntry;</span><br></pre></td></tr></table></figure>

<p>哈希表</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dictht</span> &#123;</span></span><br><span class="line">    <span class="comment">// 哈希表元素数组</span></span><br><span class="line">    dictEntry **table;</span><br><span class="line">    <span class="comment">// 哈希表大小，初始值为4</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> size;</span><br><span class="line">    <span class="comment">// 哈希表大小掩码，用于计算索引值，总是等于 size - 1</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> sizemask;</span><br><span class="line">    <span class="comment">// 该哈希表已有节点的数量</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> used;</span><br><span class="line">&#125; dictht;</span><br></pre></td></tr></table></figure>

<p>字典</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dict</span> &#123;</span></span><br><span class="line">    <span class="comment">// 类型特定操作函数</span></span><br><span class="line">    dictType *type;</span><br><span class="line">    <span class="comment">// 私有数据，保存了需要传给那些类型特定函数的可选参数</span></span><br><span class="line">    <span class="type">void</span> *privdata;</span><br><span class="line">    <span class="comment">// 哈希表，ht[1]在rehash的时候使用</span></span><br><span class="line">    dictht ht[<span class="number">2</span>];</span><br><span class="line">    <span class="comment">// rehash 索引，当 rehash 不在进行时，值为 -1</span></span><br><span class="line">    <span class="type">int</span> rehashidx;</span><br><span class="line">    <span class="comment">// 目前正在运行的安全迭代器的数量</span></span><br><span class="line">    <span class="type">int</span> iterators;</span><br><span class="line">&#125; dict;</span><br></pre></td></tr></table></figure>

<p>结构体，储存不同类型字典的操作函数指针，实现了多态</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dictType</span> &#123;</span></span><br><span class="line">    <span class="comment">// 计算哈希值的函数</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="title function_">int</span> <span class="params">(*hashFunction)</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *key)</span>;</span><br><span class="line">    <span class="comment">// 复制键的函数</span></span><br><span class="line">    <span class="type">void</span> *(*keyDup)(<span class="type">void</span> *privdata, <span class="type">const</span> <span class="type">void</span> *key);</span><br><span class="line">    <span class="comment">// 复制值的函数</span></span><br><span class="line">    <span class="type">void</span> *(*valDup)(<span class="type">void</span> *privdata, <span class="type">const</span> <span class="type">void</span> *obj);</span><br><span class="line">    <span class="comment">// 对比键的函数</span></span><br><span class="line">    <span class="type">int</span> (*keyCompare)(<span class="type">void</span> *privdata, <span class="type">const</span> <span class="type">void</span> *key1, <span class="type">const</span> <span class="type">void</span> *key2);</span><br><span class="line">    <span class="comment">// 销毁键的函数</span></span><br><span class="line">    <span class="type">void</span> (*keyDestructor)(<span class="type">void</span> *privdata, <span class="type">void</span> *key);</span><br><span class="line">    <span class="comment">// 销毁值的函数</span></span><br><span class="line">    <span class="type">void</span> (*valDestructor)(<span class="type">void</span> *privdata, <span class="type">void</span> *obj);</span><br><span class="line">&#125; dictType;</span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/2021-04-18-16187320239054.jpg" alt="-w789"></p>
<h2 id="哈希算法"><a href="#哈希算法" class="headerlink" title="哈希算法"></a>哈希算法</h2><p>redis 的 dict 本质上就是个hashmap，其中的关键是哈希算法。</p>
<p>哈希函数（英语：Hash function）又称散列算法、散列函数，是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要，使得数据量变小，将数据的格式固定下来。该函数将数据打乱混合，重新创建一个叫做散列值的指纹。</p>
<p>比如取模函数就是一种最简单的对整数的哈希算法。</p>
<p>当字典被用作数据库的底层实现，或者哈希键的底层实现时，Redis使用 <a href="https://zh.wikipedia.org/wiki/Murmur%E5%93%88%E5%B8%8C">MurmurHash2</a> 算法来计算键的哈希值。</p>
<p>具体求索引的过程</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 求哈希值</span></span><br><span class="line">hash = dict-&gt;type-&gt;hashFunction(kO);</span><br><span class="line"><span class="comment">// 根据哈希值和掩码计算出元素节点索引</span></span><br><span class="line">index = hash &amp; dict-&gt;ht[<span class="number">0</span>].sizemask</span><br></pre></td></tr></table></figure>

<h2 id="冲突解决"><a href="#冲突解决" class="headerlink" title="冲突解决"></a>冲突解决</h2><p>当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时，我们称这些键发生了冲突。</p>
<p>常见冲突解决方法</p>
<ul>
<li><p>链地址法：用链表储存冲突项</p>
</li>
<li><p>开放地址法：按照一定顺序寻找下一个可用位置(x为当前位置)</p>
<ul>
<li>线性探测法：按顺序向后查找，x+1, x+2, x+3</li>
<li>平方探测法：平方向后查找，x+1,x+4,x+9</li>
<li>双散列法</li>
</ul>
</li>
<li><p>再哈希法：依次使用多个哈希函数</p>
</li>
</ul>
<p>Redis的哈希表使用链地址法（separate chaining) 来解决键冲突，每个哈希表节点都有一个next指针，多个哈希表节点可以用 next指针构成一个单向链表，被分配到同一个索 引上的多个节点可以用这个单向链表连接起来，这就解决了键冲突的问题。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-04-18-16187328149108.jpg" alt="-w722"></p>
<p>还有一种常用的冲突解决办法是再哈希法，就是同时构造多个不同的哈希函数。<br>当H1 &#x3D; hashfunc1(key) 发生冲突时，再用H2 &#x3D; hashfunc1(key) 进行计算，直到冲突不再产生，这种方法不易产生聚集，但是增加了计算时间。</p>
<h2 id="rehash"><a href="#rehash" class="headerlink" title="rehash"></a>rehash</h2><p>随着操作的不断执行，哈希表保存的键值对会逐渐地增多或者减少，为了让哈希表的负载因子（used&#x2F;size)维持在一个合理的范围之内，程序需要对哈希表的大小进行相应的扩展或者收缩, 这个过程就是rehash。</p>
<p>这里redis采用的装载系数为1，扩容系数为2</p>
<p>Redis对字典的哈希表执行rehash的步骤如下：</p>
<ol>
<li>为字典的<code>ht[1]</code>哈希表分配空间</li>
<li>将保存在<code>ht[0]</code>中的所有键值对rehash到<code>ht[1]</code>上面：rehash指的是重新计算键的哈希值和索引值，然后将键值对放置到<code>ht[1]</code>哈希表的指定位置上</li>
<li>全部复制完成后，释放<code>ht[0]</code>，将<code>ht[1]</code>设置为<code>ht[0]</code>，重置<code>ht[1]</code></li>
</ol>
<h3 id="渐进式-rehash"><a href="#渐进式-rehash" class="headerlink" title="渐进式 rehash"></a>渐进式 rehash</h3><p>所谓渐进式，是指rehash动作并不是一次性、集中式地完成的，而是分多次、渐进式地完成的。</p>
<p>由于redis是单线程的, 哈希表里保存的键值对又可能非常多，一次性将这些键值对全部rehash到ht[1]，会导致服务器在一段时间内停止服务。</p>
<p>所以需要渐进式 rehash，在字典的每个添加、删除 、查找和更新操作的时候，顺便进行部分元素的 rehash（目前实现是rehash一个元素），避免了集中式rehash而带来的庞大计算量。</p>
<p>rehash 示例代码</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">dictRehash</span>(<span class="params">d: rDict, n: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> dictIsRehashing(d):</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (n):</span><br><span class="line">        n -= <span class="number">1</span></span><br><span class="line">        <span class="keyword">if</span> d.ht[<span class="number">0</span>].used == <span class="number">0</span>:  <span class="comment"># rehash 完成了</span></span><br><span class="line">            <span class="keyword">del</span> d.ht[<span class="number">0</span>].table</span><br><span class="line">            d.ht[<span class="number">0</span>] = c_assignment(d.ht[<span class="number">1</span>])</span><br><span class="line">            _dictReset(d.ht[<span class="number">1</span>])</span><br><span class="line">            d.rehashidx = -<span class="number">1</span></span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">assert</span> d.ht[<span class="number">0</span>].size &gt; d.rehashidx</span><br><span class="line">        <span class="comment"># 找到第一个需要移动的元素</span></span><br><span class="line">        <span class="keyword">while</span> d.ht[<span class="number">0</span>].table[d.rehashidx] <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            d.rehashidx += <span class="number">1</span></span><br><span class="line">        de = d.ht[<span class="number">0</span>].table[d.rehashidx]</span><br><span class="line">        <span class="keyword">while</span> de:  <span class="comment"># 移动该元素（包含整个冲突链表）到ht[1]</span></span><br><span class="line">            nextde = de.<span class="built_in">next</span></span><br><span class="line">            h = dictHashKey(d, de.key) &amp; d.ht[<span class="number">1</span>].sizemask</span><br><span class="line">            de.<span class="built_in">next</span> = d.ht[<span class="number">1</span>].table[h]</span><br><span class="line">            d.ht[<span class="number">1</span>].table[h] = de  <span class="comment"># 复制dictEntry元素</span></span><br><span class="line">            d.ht[<span class="number">0</span>].used -= <span class="number">1</span></span><br><span class="line">            d.ht[<span class="number">1</span>].used += <span class="number">1</span></span><br><span class="line">            de = nextde</span><br><span class="line">        d.ht[<span class="number">0</span>].table[d.rehashidx] = <span class="literal">None</span></span><br><span class="line">        d.rehashidx += <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span></span><br></pre></td></tr></table></figure>

<p>除了渐进式rehash，对于redis的多个db，也会有定时任务进行主动rehash，防止服务器长期没有执行命令时，数据库字典的 rehash 一直没办法完成。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>redis 3.0 源码</li>
<li>redis 设计与实现</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Redis</tag>
        <tag>C</tag>
      </tags>
  </entry>
  <entry>
    <title>Redis原理 —— SDS 简单动态字符串</title>
    <url>/2021/05/07/redis-source-code-data-structure-sds/</url>
    <content><![CDATA[<p>Redis没有直接使用C语言传统的字符串表示，而是自己构建了一种名为简单动态字符串SD S(simple dynamic string)的数据结构 ，并将SDS用作Redis的默认字符串表示。</p>
<p>Redis内部所有字符串都由SDS来表示，其本质就是动态字节数组，和python的<code>bytearray</code>类似。</p>
<span id="more"></span>

<h2 id="SDS-实现"><a href="#SDS-实现" class="headerlink" title="SDS 实现"></a>SDS 实现</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 类型别名，用于指向 sdshdr 的 buf 属性, 用以和C字符串兼容</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">char</span> *sds;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 保存字符串对象的结构</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> &#123;</span></span><br><span class="line">    <span class="comment">// buf 中已占用空间的长度，不包含末尾的\0</span></span><br><span class="line">    <span class="type">int</span> len;</span><br><span class="line">    <span class="comment">// buf 中剩余可用空间的长度</span></span><br><span class="line">    <span class="type">int</span> <span class="built_in">free</span>;</span><br><span class="line">    <span class="comment">// 数据空间, 柔性数组, 存储C字符串</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/2021-05-07-16203880544303.jpg"></p>
<p>使用SDS时，一般是通过指向buf数组的指针而不是sdshdr，这样相关接口就和C字符串兼容。同时需要使用到len和free相关属性时，通过计算指针偏移来得到sdshdr指针，整体设计比较高效。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">size_t</span> <span class="title function_">sdslen</span><span class="params">(<span class="type">const</span> sds s)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> *<span class="title">sh</span> =</span> (<span class="type">void</span>*)(s-(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> sdshdr)));</span><br><span class="line">    <span class="keyword">return</span> sh-&gt;len;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="创建新SDS"><a href="#创建新SDS" class="headerlink" title="创建新SDS"></a>创建新SDS</h2><p>创建比较简单，注意buf末尾的<code>\0</code>，以及最后返回的buf指针</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">sds <span class="title function_">sdsnewlen</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *init, <span class="type">size_t</span> initlen)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> *<span class="title">sh</span>;</span></span><br><span class="line">    <span class="comment">// 根据是否有初始化内容，选择适当的内存分配方式</span></span><br><span class="line">    <span class="keyword">if</span> (init) &#123;</span><br><span class="line">        <span class="comment">// zmalloc 不初始化所分配的内存</span></span><br><span class="line">        sh = zmalloc(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> sdshdr)+initlen+<span class="number">1</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// zcalloc 将分配的内存全部初始化为 0</span></span><br><span class="line">        sh = zcalloc(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> sdshdr)+initlen+<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (sh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">// 设置初始化长度</span></span><br><span class="line">    sh-&gt;len = initlen;</span><br><span class="line">    <span class="comment">// 新 sds 不预留任何空间</span></span><br><span class="line">    sh-&gt;<span class="built_in">free</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 如果有指定初始化内容，将它们复制到 sdshdr 的 buf 中</span></span><br><span class="line">    <span class="keyword">if</span> (initlen &amp;&amp; init)</span><br><span class="line">        <span class="built_in">memcpy</span>(sh-&gt;buf, init, initlen);</span><br><span class="line">    <span class="comment">// 以 \0 结尾</span></span><br><span class="line">    sh-&gt;buf[initlen] = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    <span class="comment">// 返回 buf 部分，而不是整个 sdshdr</span></span><br><span class="line">    <span class="keyword">return</span> (<span class="type">char</span>*)sh-&gt;buf;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="容量调整"><a href="#容量调整" class="headerlink" title="容量调整"></a>容量调整</h2><p>既然是动态数组，就会涉及到容量调整。<br>Redis的调整策略，当所需空间小于SDS_MAX_PREALLOC(当前版本是1MB)时是指数增长, 否则线性增长SDS_MAX_PREALLOC的大小。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">sds <span class="title function_">sdsMakeRoomFor</span><span class="params">(sds s, <span class="type">size_t</span> addlen)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> *<span class="title">sh</span>, *<span class="title">newsh</span>;</span></span><br><span class="line">    <span class="comment">// 获取 s 目前的空余空间长度</span></span><br><span class="line">    <span class="type">size_t</span> <span class="built_in">free</span> = sdsavail(s);</span><br><span class="line">    <span class="type">size_t</span> len, newlen;</span><br><span class="line">    <span class="comment">// s 目前的空余空间已经足够，无须再进行扩展，直接返回</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">free</span> &gt;= addlen) <span class="keyword">return</span> s;</span><br><span class="line">    <span class="comment">// 获取 s 目前已占用空间的长度</span></span><br><span class="line">    len = sdslen(s);</span><br><span class="line">    sh = (<span class="type">void</span>*) (s-(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> sdshdr)));</span><br><span class="line">    <span class="comment">// s 最少需要的长度</span></span><br><span class="line">    newlen = (len+addlen);</span><br><span class="line">    <span class="comment">// 根据新长度，为 s 分配新空间所需的大小</span></span><br><span class="line">    <span class="comment">// 小于SDS_MAX_PREALLOC(当前版本是1MB)时是指数增长, 否则线性增长</span></span><br><span class="line">    <span class="keyword">if</span> (newlen &lt; SDS_MAX_PREALLOC)</span><br><span class="line">        newlen *= <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        newlen += SDS_MAX_PREALLOC;</span><br><span class="line">    <span class="comment">// 重新调整空间大小</span></span><br><span class="line">    newsh = zrealloc(sh, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> sdshdr)+newlen+<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (newsh == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">// 更新 sds 的空余长度</span></span><br><span class="line">    newsh-&gt;<span class="built_in">free</span> = newlen - len;</span><br><span class="line">    <span class="keyword">return</span> newsh-&gt;buf;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>redis 3.0 源码</li>
<li>redis 设计与实现</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Redis</tag>
        <tag>C</tag>
      </tags>
  </entry>
  <entry>
    <title>Redis原理 —— ziplist 压缩列表</title>
    <url>/2021/05/22/redis-source-code-data-structure-ziplist/</url>
    <content><![CDATA[<p>压缩列表(ziplist)是列表和哈希的底层实现之一，是为尽可能地节约内存而设计的特殊编码双端链表。 当一个列表只包含少量列表项，并且每个列表项要么就是小整数值，要么就是长度比较短的字符串，那么Redis就会使用压缩列表来做列表键的底层实现。</p>
<p>压缩列表的优点是节省内存，缺点是插入元素的复杂度较高<code>平均O(N)最坏O(N^2)</code>, 但是在小数据量的情况下，这种复杂度也是可以接受的。</p>
<span id="more"></span>

<h2 id="ziplist-结构"><a href="#ziplist-结构" class="headerlink" title="ziplist 结构"></a>ziplist 结构</h2><p>压缩列表是由一系列<code>entry</code>组成的结构。<code>entry</code>记录了当前节点的大小和前置节点的大小，所以可以双向插入和遍历。</p>
<p>ziplist 又4个主要部分组成</p>
<ul>
<li>zlbytes: 4字节，表示整个列表占用内存大小</li>
<li>zltail: 4字节，表示列表尾节点相对列表第一个字节的偏移量</li>
<li>zllen: 2字节，表示列表元素个数，如果节点个数超出2^16-1则需要遍历列表求出元素个数。</li>
<li>zlend: 1字节<code>0xFF</code>, 表示列表末尾</li>
</ul>
<p>ziplist 结构示例图</p>
<figure class="highlight haskell"><table><tr><td class="code"><pre><span class="line"><span class="title">area</span>        |&lt;---- ziplist header ----&gt;|&lt;----------- entries -------------&gt;|&lt;-end-&gt;|</span><br><span class="line"></span><br><span class="line"><span class="title">size</span>          <span class="number">4</span> bytes  <span class="number">4</span> bytes  <span class="number">2</span> bytes    ?        ?        ?        ?     <span class="number">1</span> byte</span><br><span class="line">            +---------+--------+-------+--------+--------+--------+--------+-------+</span><br><span class="line"><span class="title">component</span>   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |</span><br><span class="line">            +---------+--------+-------+--------+--------+--------+--------+-------+</span><br><span class="line">                                       ^                          ^        ^</span><br><span class="line"><span class="title">address</span>                                |                          |        |</span><br><span class="line">                                <span class="type">ZIPLIST_ENTRY_HEAD</span>                |   <span class="type">ZIPLIST_ENTRY_END</span></span><br><span class="line">                                                                  |</span><br><span class="line">                                                        <span class="type">ZIPLIST_ENTRY_TAIL</span></span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/2021-05-22-16216774199505.jpg" alt="-w1224"></p>
<p>上图我们向ziplist添加了3个entry元素，向list头部插入（redis内部使用时一般向尾部插入），后面会详细解析这些元素。</p>
<h3 id="ziplist-节点元素"><a href="#ziplist-节点元素" class="headerlink" title="ziplist 节点元素"></a>ziplist 节点元素</h3><p>每个ziplist节点由一下3个部分组成</p>
<ul>
<li>prelen: 前一个节点长度，单位为字节</li>
<li>encoding: 节点的编码类型，</li>
<li>content: 节点内容，可能是字节数组(c语言字符串去除末尾的\0)或者数组</li>
</ul>
<p>之前的3个节点的二进制详情<br><img data-src="https://image.ponder.work/mweb/2021-05-22-16216773421053.jpg" alt="-w678"></p>
<p>节点迭代器结构体 zlentry</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zlentry</span> &#123;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> prevrawlensize;  <span class="comment">// 编码 prevrawlen 所需的字节大小</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> prevrawlen;  <span class="comment">// 前置节点的长度</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> lensize; <span class="comment">// 编码 len 所需的字节大小</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> len;  <span class="comment">// 当前节点值的长度</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> headersize;  <span class="comment">// 当前节点 header 的大小, 等于prevrawlensize + lensize</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> encoding;  <span class="comment">// 当前节点值所使用的编码类型</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> *p;   <span class="comment">// 指向当前节点的指针，也就是内存entry的prelen字段</span></span><br><span class="line">&#125; zlentry;</span><br></pre></td></tr></table></figure>

<p><strong>注意</strong>：zlentry结构体和ziplist中实际存储的entry结构是不一样的，zlentry只是为了遍历时操作entry时便利一些，类似序列化和反序列化。在需要对entry操作时，把对应位置的信息取出存到zlentry结构体中</p>
<h3 id="prelen"><a href="#prelen" class="headerlink" title="prelen"></a>prelen</h3><p>prelen 记录了以字节为单位的前一个节点长度，有两种情况</p>
<ol>
<li>默认占用1字节空间，表示0到253</li>
<li>如果节点长度大于253，则这个字节就设置为254(0xFE)作为标志位, 随后的4个字节存储实际长度。</li>
</ol>
<p>255这个数字为啥舍弃不用呢？因为255已经作为列表结束的标志位，避免出现误导。</p>
<h3 id="encoding"><a href="#encoding" class="headerlink" title="encoding"></a>encoding</h3><p>encoding 记录了当前节点的编码类型，编码时先尝试将内容转成数字，失败则当做字符串处理。</p>
<p>个人觉得ziplist的精华就在entry的encoding，对让内存的每一个bit都重复表示了信息。</p>
<p>下表中的0和1表示具体的二进制位, b表示该位置可能为0或者1</p>
<table>
<thead>
<tr>
<th>编码</th>
<th>占用空间&#x2F;字节</th>
<th>表示类型</th>
<th>具体含义</th>
</tr>
</thead>
<tbody><tr>
<td>00bbbbbb</td>
<td>1</td>
<td>字节数组</td>
<td>content的长度为6bit, 也就是0-63</td>
</tr>
<tr>
<td>01bbbbbb bbbbbbbb</td>
<td>2</td>
<td>字节数组</td>
<td>content的长度为14bit, 也就是0-16383</td>
</tr>
<tr>
<td>10000000 bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb</td>
<td>5</td>
<td>字节数组</td>
<td>content的长度为32bit, 也就是0-4294967295</td>
</tr>
<tr>
<td>11110001 到 11111101</td>
<td>1</td>
<td>数字</td>
<td>用4个bit直接表示数字0-12， content长度为0</td>
</tr>
<tr>
<td>11111110</td>
<td>1</td>
<td>数字</td>
<td>content为int8_t, 长度2字节</td>
</tr>
<tr>
<td>11000000</td>
<td>1</td>
<td>数字</td>
<td>content为int16_t, 长度2字节</td>
</tr>
<tr>
<td>11010000</td>
<td>1</td>
<td>数字</td>
<td>content为int32_t, 长度4字节</td>
</tr>
<tr>
<td>11100000</td>
<td>1</td>
<td>数字</td>
<td>content为int64_t, 长度8字节</td>
</tr>
<tr>
<td>11110000</td>
<td>1</td>
<td>数字</td>
<td>content为24bit有符号整数, 长度3字节</td>
</tr>
</tbody></table>
<p>可以看到ziplist为了节省内存空间，表示信息时真是细扣到每一个bit，非常高效。<br>但是也有个不足，就是代码变得复杂了。</p>
<p>由于prelen和encoding和content这3个部分都是变长的，每一次插入和删除元素都得计算列表内存长度的变化。</p>
<p>而且由于prelen的变长，可能会触发后面所有节点连锁更新prelen的值.<br>本来节点插入时只需要复制一次该节点以后所有节点的内存，这时复杂度为O(n), 触发连锁更新之后，这时候列表的插入复杂度就会变为O(n^2)。</p>
<h2 id="list插入"><a href="#list插入" class="headerlink" title="list插入"></a>list插入</h2><p>当list底层实现为ziplist时，插入原始的逻辑<br>主要涉及到各种长度和偏移量的计算，比较繁琐</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">unsigned</span> <span class="type">char</span> *__ziplistInsert(<span class="type">unsigned</span> <span class="type">char</span> *zl, <span class="type">unsigned</span> <span class="type">char</span> *p, <span class="type">unsigned</span> <span class="type">char</span> *s, <span class="type">unsigned</span> <span class="type">int</span> slen) &#123;</span><br><span class="line">    <span class="comment">// 记录当前 ziplist 的长度</span></span><br><span class="line">    <span class="type">size_t</span> curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, prevlen = <span class="number">0</span>;</span><br><span class="line">    <span class="type">size_t</span> offset;</span><br><span class="line">    <span class="type">int</span> nextdiff = <span class="number">0</span>;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> encoding = <span class="number">0</span>;</span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> value = <span class="number">123456789</span>;   <span class="comment">// 默认值，megic num， 便于debug</span></span><br><span class="line">    zlentry entry, tail;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (p[<span class="number">0</span>] != ZIP_END) &#123;</span><br><span class="line">        <span class="comment">// 列表非空，并且 p 正指向列表的其中一个节点</span></span><br><span class="line">        <span class="comment">// 取出 p 所指向节点的信息，并将它保存到 entry 结构中；用 prevlen 变量记录前置节点的长度</span></span><br><span class="line">        entry = zipEntry(p);</span><br><span class="line">        prevlen = entry.prevrawlen;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 列表为空，或者列表不为空且p正好指向表尾</span></span><br><span class="line">        <span class="type">unsigned</span> <span class="type">char</span> *ptail = ZIPLIST_ENTRY_TAIL(zl);</span><br><span class="line">        <span class="keyword">if</span> (ptail[<span class="number">0</span>] != ZIP_END) &#123;</span><br><span class="line">            <span class="comment">// 列表不为空且p正好指向表尾，取出表尾节点的长度</span></span><br><span class="line">            prevlen = zipRawEntryLength(ptail);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 尝试将s转换为数字类型，并给出content的长度</span></span><br><span class="line">    <span class="keyword">if</span> (zipTryEncoding(s,slen,&amp;value,&amp;encoding)) &#123;</span><br><span class="line">        <span class="comment">// 转换成数字成功，结果会保存在value中</span></span><br><span class="line">        reqlen = zipIntSize(encoding);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 转换失败</span></span><br><span class="line">        reqlen = slen;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 计算prelen的长度</span></span><br><span class="line">    reqlen += zipPrevEncodeLength(<span class="literal">NULL</span>,prevlen);</span><br><span class="line">    <span class="comment">// 计算encoding和content的长度</span></span><br><span class="line">    reqlen += zipEncodeLength(<span class="literal">NULL</span>,encoding,slen);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 只要新节点不是被添加到列表末端，就需要确认 p 所指向的节点的prelen是否足够大</span></span><br><span class="line">    <span class="comment">// nextdiff 保存了新旧编码之间的字节大小差，如果大于 0，需要进行扩展</span></span><br><span class="line">    nextdiff = (p[<span class="number">0</span>] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 因为重分配空间，该可能会改变 zl 的地址，p指针可能会失效，需要记录 zl 到 p 的偏移量</span></span><br><span class="line">    offset = p-zl;</span><br><span class="line">    zl = ziplistResize(zl,curlen+reqlen+nextdiff);</span><br><span class="line">    p = zl+offset;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (p[<span class="number">0</span>] != ZIP_END) &#123;</span><br><span class="line">        <span class="comment">// 新元素之后还有节点，因为新元素的加入，需要对这些原有节点进行调整</span></span><br><span class="line">        <span class="comment">// 移动现有元素，为新元素的插入空间腾出位置</span></span><br><span class="line">        memmove(p+reqlen,p-nextdiff,curlen-offset<span class="number">-1</span>+nextdiff);</span><br><span class="line">        <span class="comment">// 将新节点的长度编码至后置节点</span></span><br><span class="line">        zipPrevEncodeLength(p+reqlen,reqlen);</span><br><span class="line">        <span class="comment">// 更新到达表尾的偏移量，将新节点的长度也算上</span></span><br><span class="line">        ZIPLIST_TAIL_OFFSET(zl) =</span><br><span class="line">            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);</span><br><span class="line">        <span class="comment">// 更新zltail</span></span><br><span class="line">        tail = zipEntry(p+reqlen);</span><br><span class="line">        <span class="keyword">if</span> (p[reqlen+tail.headersize+tail.len] != ZIP_END) &#123;</span><br><span class="line">            ZIPLIST_TAIL_OFFSET(zl) =</span><br><span class="line">                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 新元素是新的表尾节点, 更新zltail</span></span><br><span class="line">        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当 nextdiff != 0 时，需要级联地更新后续的节点</span></span><br><span class="line">    <span class="keyword">if</span> (nextdiff != <span class="number">0</span>) &#123;</span><br><span class="line">        offset = p-zl;</span><br><span class="line">        <span class="comment">// T  = O(N^2)</span></span><br><span class="line">        zl = __ziplistCascadeUpdate(zl,p+reqlen);</span><br><span class="line">        p = zl+offset;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 一切搞定，将前置节点的长度写入新节点的 header</span></span><br><span class="line">    p += zipPrevEncodeLength(p,prevlen);</span><br><span class="line">    <span class="comment">// 将节点值的长度写入新节点的 header</span></span><br><span class="line">    p += zipEncodeLength(p,encoding,slen);</span><br><span class="line">    <span class="comment">// 写入节点值</span></span><br><span class="line">    <span class="keyword">if</span> (ZIP_IS_STR(encoding)) &#123;</span><br><span class="line">        <span class="built_in">memcpy</span>(p,s,slen);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        zipSaveInteger(p,value,encoding);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 更新zllen</span></span><br><span class="line">    ZIPLIST_INCR_LENGTH(zl,<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> zl;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="list底层转换为链表"><a href="#list底层转换为链表" class="headerlink" title="list底层转换为链表"></a>list底层转换为链表</h2><p>判断是不是要把list转换为链表</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">listTypeTryConversion</span><span class="params">(robj *subject, robj *value)</span> &#123;</span><br><span class="line">    <span class="comment">// 确保 subject 为 ZIPLIST 编码</span></span><br><span class="line">    <span class="keyword">if</span> (subject-&gt;encoding != REDIS_ENCODING_ZIPLIST) <span class="keyword">return</span>;</span><br><span class="line">    <span class="keyword">if</span> (sdsEncodedObject(value) &amp;&amp;</span><br><span class="line">        <span class="comment">// 看字符串是否过长</span></span><br><span class="line">        sdslen(value-&gt;ptr) &gt; server.list_max_ziplist_value)</span><br><span class="line">            <span class="comment">// 将编码转换为双端链表</span></span><br><span class="line">            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">listTypeConvert</span><span class="params">(robj *subject, <span class="type">int</span> enc)</span> &#123;</span><br><span class="line">    listTypeIterator *li;</span><br><span class="line">    listTypeEntry entry;</span><br><span class="line">    <span class="keyword">if</span> (enc == REDIS_ENCODING_LINKEDLIST) &#123;</span><br><span class="line">        <span class="built_in">list</span> *l = listCreate();</span><br><span class="line">        listSetFreeMethod(l,decrRefCountVoid);</span><br><span class="line">        <span class="comment">// 遍历 ziplist ，并将里面的值全部添加到双端链表中</span></span><br><span class="line">        li = listTypeInitIterator(subject,<span class="number">0</span>,REDIS_TAIL);</span><br><span class="line">        <span class="keyword">while</span> (listTypeNext(li,&amp;entry)) listAddNodeTail(l,listTypeGet(&amp;entry));</span><br><span class="line">        listTypeReleaseIterator(li);</span><br><span class="line">        <span class="comment">// 更新编码</span></span><br><span class="line">        subject-&gt;encoding = REDIS_ENCODING_LINKEDLIST;</span><br><span class="line">        <span class="comment">// 释放原来的 ziplist</span></span><br><span class="line">        zfree(subject-&gt;ptr);</span><br><span class="line">        subject-&gt;ptr = l;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        redisPanic(<span class="string">&quot;Unsupported list conversion&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>redis 3.0 源码</li>
<li>redis 设计与实现</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Redis</tag>
        <tag>C</tag>
      </tags>
  </entry>
  <entry>
    <title>Redis原理 —— zskiplist 跳跃表</title>
    <url>/2021/04/20/redis-source-code-data-structure-zskiplist/</url>
    <content><![CDATA[<p>跳跃表 （skiplist) 是一种有序数据结构，它通过在每个节点中维持多个指向其他节点的 指针，从而达到快速访问节点的目的。</p>
<p>跳跃表优点</p>
<ul>
<li>表支持平均<code>O(logN)</code>, 最坏<code>O(N)</code>复杂度的节点查找，效率可以和平衡树相当</li>
<li>通过顺序性操作来批量处理节点</li>
<li>实现比平衡树要来得更为简单</li>
</ul>
<p>因为<code>ziplist</code>内存占用较小，所以Redis使用作为有序集合的初始底层结构。<br>如果一个有序集合包含的元素数量比较多（大于<code>zset-max-ziplist-entries</code>），又或者有序集合中元素的成员是比较长的字符串时（大于<code>zset-max-ziplist-value</code>），Redis就会将其底层结构转换为跳跃表。</p>
<span id="more"></span>

<h2 id="zskiplist-数据结构"><a href="#zskiplist-数据结构" class="headerlink" title="zskiplist 数据结构"></a>zskiplist 数据结构</h2><p>跳跃表节点, 其中<code>zskiplistLevel</code>成员是<a href="https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html">柔性数组</a></p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> &#123;</span></span><br><span class="line">    <span class="comment">// 成员对象</span></span><br><span class="line">    robj *obj;</span><br><span class="line">    <span class="comment">// 分值</span></span><br><span class="line">    <span class="type">double</span> score;</span><br><span class="line">    <span class="comment">// 后退指针</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">backward</span>;</span></span><br><span class="line">    <span class="comment">// 层</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistLevel</span> &#123;</span></span><br><span class="line">        <span class="comment">// 前进指针</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">forward</span>;</span></span><br><span class="line">        <span class="comment">// 跨度，经过的节点数目</span></span><br><span class="line">        <span class="type">unsigned</span> <span class="type">int</span> span;</span><br><span class="line">    &#125; level[];  <span class="comment">// 柔性数组</span></span><br><span class="line">&#125; zskiplistNode;</span><br></pre></td></tr></table></figure>

<p>跳跃表</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zskiplist</span> &#123;</span></span><br><span class="line">    <span class="comment">// 表头节点和表尾节点</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">header</span>, *<span class="title">tail</span>;</span></span><br><span class="line">    <span class="comment">// 表中节点的数量</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> length;</span><br><span class="line">    <span class="comment">// 表中层数最大的节点的层数</span></span><br><span class="line">    <span class="type">int</span> level;</span><br><span class="line">&#125; zskiplist;</span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/2021-04-23-16190066207268.jpg"></p>
<p>所谓跳跃表，就是多层链表（redis中的实现是最多32层）通过额外的链接提高效率，从低层到高层，节点之间的跨度逐渐变大。</p>
<p>跨度越大则查找效率越高，所以查找时是从高层往底层查找。</p>
<p>如果节点的最高层高为x，则可以认为该节点就存储在低x层，则表头到该节点的跨度之和为该节点的rank(排位)，所有节点的最大层高为跳跃表层高。</p>
<h2 id="跳跃表插入节点"><a href="#跳跃表插入节点" class="headerlink" title="跳跃表插入节点"></a>跳跃表插入节点</h2><p>因为跳跃表是多层链表，所以插入节点的关键是找到每一层插入的位置，以及插入位置的跨度变化，还有新节点的跨度计算。</p>
<p>python 版跳跃表插入实现</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 比较节点大小</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">_node_lt</span>(<span class="params">node: zskiplistNode, score: <span class="built_in">float</span>, obj: robj</span>):</span><br><span class="line">    <span class="keyword">if</span> node.score &lt; score:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">if</span> (node.score == score <span class="keyword">and</span></span><br><span class="line">        compareStringObjects(node.obj, obj) &lt; <span class="number">0</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">zslInsert</span>(<span class="params">zsl: zskiplist, score: <span class="built_in">float</span>, obj: robj</span>) -&gt; zskiplistNode:</span><br><span class="line">    <span class="comment"># update list记录的是每一层, 新节点需要插入的位置(新节点x的backward节点指针)</span></span><br><span class="line">    update: <span class="type">List</span>[Opt[zskiplistNode]] = [<span class="literal">None</span> <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(ZSKIPLIST_MAXLEVEL)]</span><br><span class="line">    <span class="comment"># rank[i]: 从高到低, 到第i层为止经过的所有node的span总和, 也就是节点的排序</span></span><br><span class="line">    <span class="comment"># 用于计算新节点各层的span, 以及新节点的后继节点各层的span</span></span><br><span class="line">    rank = [<span class="number">0</span> <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(ZSKIPLIST_MAXLEVEL)]</span><br><span class="line">    x = zsl.header</span><br><span class="line">    <span class="comment"># 从高层开始遍历</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(zsl.level-<span class="number">1</span>, -<span class="number">1</span>, -<span class="number">1</span>):</span><br><span class="line">        rank[i] = <span class="number">0</span> <span class="keyword">if</span> i == zsl.level-<span class="number">1</span> <span class="keyword">else</span> rank[i+<span class="number">1</span>]</span><br><span class="line">        <span class="comment"># 找到每一层x需要插入的位置, 并更新rank</span></span><br><span class="line">        <span class="keyword">while</span> x.level[i].forward <span class="keyword">and</span> _node_lt(x.level[i].forward, score, obj):</span><br><span class="line">            rank[i] += x.level[i].span</span><br><span class="line">            x = x.level[i].forward</span><br><span class="line">        <span class="comment"># 对于每一层i, 新节点会插入到update[i].level[i]之后</span></span><br><span class="line">        update[i] = x</span><br><span class="line">    level = zslRandomLevel()  <span class="comment"># 取一个随机层数, 使zskiplist，每层节点更为均衡</span></span><br><span class="line">    <span class="comment"># 新节点层高增大的情况，更新扩展层的默认跨度</span></span><br><span class="line">    <span class="keyword">if</span> level &gt; zsl.level:</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(zsl.level, level):</span><br><span class="line">            rank[i] = <span class="number">0</span></span><br><span class="line">            update[i] = zsl.header</span><br><span class="line">            update[i].level[i].span = zsl.length</span><br><span class="line">        zsl.level = level</span><br><span class="line">    <span class="comment"># 更新节点x和前驱节点已有层的跨度</span></span><br><span class="line">    x = zslCreateNode(level, score, obj)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(level):</span><br><span class="line">        x.level[i].forward = update[i].level[i].forward</span><br><span class="line">        update[i].level[i].forward = x</span><br><span class="line">        x.level[i].span = update[i].level[i].span - (rank[<span class="number">0</span>] - rank[i])</span><br><span class="line">        update[i].level[i].span = (rank[<span class="number">0</span>] - rank[i]) + <span class="number">1</span></span><br><span class="line">    <span class="comment"># 更新前驱节点扩展层的跨度，x节点这些层没有后继节点，所以跨度为0</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(level, zsl.level):</span><br><span class="line">        update[i].level[i].span += <span class="number">1</span>  <span class="comment"># type: ignore</span></span><br><span class="line">    <span class="comment"># 设置新节点的后退指针, level[0]才有后退指针</span></span><br><span class="line">    x.backward = <span class="literal">None</span> <span class="keyword">if</span> update[<span class="number">0</span>] == zsl.header <span class="keyword">else</span> update[<span class="number">0</span>]</span><br><span class="line">    <span class="keyword">if</span> x.level[<span class="number">0</span>].forward:</span><br><span class="line">        x.level[<span class="number">0</span>].forward.backward = x</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        zsl.tail = x</span><br><span class="line">    zsl.length += <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> x</span><br></pre></td></tr></table></figure>

<h2 id="跳跃表查找"><a href="#跳跃表查找" class="headerlink" title="跳跃表查找"></a>跳跃表查找</h2><p>跳跃表的查找则是从高层向低层查找，沿着最高层链表前进；遇到大于目标值的节点，则往下一层，直到找到相等的值为止。</p>
<p>经过的所有节点的跨度相加即是目标节点的rank。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">zslGetRank</span>(<span class="params">zsl: zskiplist, score: <span class="built_in">float</span>, obj: robj</span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    rank = <span class="number">0</span></span><br><span class="line">    x = zsl.header</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(zsl.level-<span class="number">1</span>, -<span class="number">1</span>, -<span class="number">1</span>):</span><br><span class="line">        <span class="keyword">while</span> x.level[i].forward <span class="keyword">and</span> _node_lt(x.level[i].forward, score, obj):</span><br><span class="line">            rank += x.level[i].span</span><br><span class="line">            x = x.level[i].forward</span><br><span class="line">        <span class="keyword">if</span> x.obj <span class="keyword">and</span> equalStringObjects(x.obj, obj):</span><br><span class="line">            <span class="keyword">return</span> rank</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br></pre></td></tr></table></figure>

<p>查找score&#x3D;2.0的o2对象的过程<br><img data-src="https://image.ponder.work/mweb/2021-04-23-16191635872238.jpg"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>redis 3.0 源码</li>
<li>redis 设计与实现</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Redis</tag>
        <tag>C</tag>
      </tags>
  </entry>
  <entry>
    <title>Redis持久化：RDB与AOF</title>
    <url>/2021/07/20/redis-source-code-rdb-aof-persistence/</url>
    <content><![CDATA[<p>Redis是内存型数据库，所有数据都存储在内存中。而内存是易失型存储，一旦进程退出所有数据都会丢失。</p>
<p>所谓持久化，就是将Redis在内存中的数据库状态以某次格式保存到磁盘里面，避免数据意外丢失。</p>
<p>Redis有两种持久化方式：RDB (Redis Database)、AOF (Append Only File)</p>
<span id="more"></span>
<h2 id="RDB持久化"><a href="#RDB持久化" class="headerlink" title="RDB持久化"></a>RDB持久化</h2><p>RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件，包含了Redis数据库的所有数据。</p>
<p>RDB是将redis中所有db中的所有键值对以如下格式进行储存</p>
<p><img data-src="https://image.ponder.work/mweb/2021-07-21-16268563558568.jpg"></p>
<h3 id="RDB文件创建"><a href="#RDB文件创建" class="headerlink" title="RDB文件创建"></a>RDB文件创建</h3><p>有两个命令可以生成RDB文件，<code>SAVE</code> 和 <code>BGSAVE</code>。生成RDB文件时，redis会遍历所有非空db的所有键值对按一定格式存储到RDB文件中。</p>
<p><code>SAVE</code> 命令会在当前进程进行，期间服务器会阻塞，不能处理任何请求。<br><code>BGSAVE</code> 命令会fork一个子进程来创建RDB（利用Copy-on-write），服务继续处理命令请求。</p>
<p>为了避免竞争条件和性能问题，<code>SAVE</code> 和 <code>BGSAVE</code>任意时刻只能有一个在执行。</p>
<p>用户可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足，就会触发RDB保存.<br>save选项的格式是 <code>save seconds option_times</code>。例如<code>save 900 1</code>，若服务器在900秒之内, 对数据库进行了至少1次修改，则执行BGSAVE。</p>
<p>BGSAVE也已可能会阻塞请求，因为磁盘io满了，这时如果有fsync操作，服务也会阻塞。<br>可以设置 <code>no-appendfsync-on-rewrite yes</code>, 在子进程处理和写硬盘时, 主进程不调用 fsync() 操作。</p>
<h3 id="RDB文件读取"><a href="#RDB文件读取" class="headerlink" title="RDB文件读取"></a>RDB文件读取</h3><p>服务器启动时会自动载入RDB文件，Redis并没有专门用于载人RDB文件的命令。</p>
<p>如果服务器开启了AOF持久化功能，那么服务器会优先使用AOF文件来还原数据库状态。</p>
<h2 id="AOF持久化"><a href="#AOF持久化" class="headerlink" title="AOF持久化"></a>AOF持久化</h2><p>由于RDB生成的机制决定了，RDB文件总是会和redis内存有部分不一致，<strong>RDB文件会缺少从上次BGSAVE开始到当前时刻的所有改动</strong>。AOF持久化的存在就是为了解决该问题。</p>
<p>AOF持久化是通过保存执行的写命令来记录数据库状态。<br>因为Redis的命令请求协议是纯文本格式，所以AOF文件类似如下。</p>
<figure class="highlight moonscript"><table><tr><td class="code"><pre><span class="line">*<span class="number">2</span>\r\n$<span class="number">6</span>\r\nSELECT\r\n$l\r\nO\r\n</span><br><span class="line">*<span class="number">3</span>\r\n$<span class="number">3</span>\r\nSET\r\n$<span class="number">3</span>\r\nmsg\r\n$<span class="number">5</span>\r\nhello\r\n</span><br></pre></td></tr></table></figure>

<h3 id="AOF持久化实现"><a href="#AOF持久化实现" class="headerlink" title="AOF持久化实现"></a>AOF持久化实现</h3><ol>
<li>命令追加：执行完命令，将所有<strong>写命令</strong>追加到aof_buf缓冲区末尾</li>
<li>文件写入：将aof_buf的内容写入文件，并清空aof_buf</li>
<li>文件同步：清空完aof_buf之后，根据appendfsync配置的策略，决定如何刷新文件缓存到硬盘。</li>
</ol>
<p>appendfsync决定如何刷新文件缓存到硬盘，该选项的值直接影响的效率和安全性。<br>当故障停机时，文件缓冲区内的数据会丢失。<br>appendfsync有以下选项</p>
<ul>
<li>always: 每次都会进行文件缓冲区刷新，最安全，效率也最低。</li>
<li>no: 不主动刷新文件缓冲区，由系统决定刷新时机，安全性最低，效率最高</li>
<li>everysec: 每秒刷新一次文件缓冲区，安全性和效率的折中方案。</li>
</ul>
<h3 id="AOF文件还原"><a href="#AOF文件还原" class="headerlink" title="AOF文件还原"></a>AOF文件还原</h3><p>AOF的还原就是模仿客户端逐条执行文件里的命令。</p>
<p>AOF的还原时机也是服务启动时，并且在还原过程中能正常执行的只有 PUBSUB 等模块。</p>
<p>步骤如下：</p>
<ol>
<li>创建不带网络连接的伪客户端(fake client)</li>
<li>从AOF文件中分析和读取一条命令</li>
<li>使用伪客户端执行该命令</li>
<li>重复步骤2、3，直到处理完所有命令</li>
</ol>
<h3 id="AOF重写"><a href="#AOF重写" class="headerlink" title="AOF重写"></a>AOF重写</h3><p>由于AOF是直接记录的写命令而不是数据库状态，所以文件中包含很多冗余语句，导致文件膨胀。</p>
<p>比如下面的这些命令，其实最终数据库状态等价于<code>lpush numbers 333</code>, 前4条语句都是冗余的。</p>
<figure class="highlight accesslog"><table><tr><td class="code"><pre><span class="line"><span class="number">127.0.0.1:6379</span>&gt; lpush numbers <span class="number">111</span></span><br><span class="line">(integer) <span class="number">1</span></span><br><span class="line"><span class="number">127.0.0.1:6379</span>&gt; lpush numbers <span class="number">222</span></span><br><span class="line">(integer) <span class="number">2</span></span><br><span class="line"><span class="number">127.0.0.1:6379</span>&gt; lpop numbers</span><br><span class="line"><span class="string">&quot;222&quot;</span></span><br><span class="line"><span class="number">127.0.0.1:6379</span>&gt; lpop numbers</span><br><span class="line"><span class="string">&quot;111&quot;</span></span><br><span class="line"><span class="number">127.0.0.1:6379</span>&gt; lpush numbers <span class="number">333</span></span><br><span class="line">(integer) <span class="number">1</span></span><br><span class="line"><span class="number">127.0.0.1:6379</span>&gt; lrange numbers <span class="number">0</span> -<span class="number">1</span></span><br><span class="line"><span class="number">1</span>) <span class="string">&quot;333&quot;</span></span><br></pre></td></tr></table></figure>

<p>为了解决AOF文件体积膨胀的问题，Redis提供了AOF文件重写(rewrite) 功能。</p>
<p>AOF文件重写是遍历redis的所有键值对，生成对应的redis命令，写入到一个新的文件中，并替换旧AOF文件。<br>所以AOF文件重写和旧AOF文件并没有关系，更应该称之为<strong>AOF重生成</strong>。</p>
<p>AOF重写程序在子进程里执行, 这样做可以同时达到两个目的:</p>
<ul>
<li>子进程进行AOF重写期间, 服务器进程可以继续处理命令请求。</li>
<li>子进程带有服务器进程的数据副本，可以在避免使用锁的情况下，保证数据的安全性（Copy-on-write）。</li>
</ul>
<p>在AOF重新过程中，所有命令会额外会写一份到<strong>AOF重写缓冲区</strong>中，当新AOF文件生成时，父进程会将AOF重写缓冲区的内容追加到新AOF文件中，并替换旧AOF文件。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-07-21-16268637141893.jpg"></p>
<p>为防止AOF重写失败，AOF缓冲区在重写过程中依然正常工作。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-07-21-16268635658876.jpg"></p>
<p><strong>注意</strong>：redis主从是基于<code>RDB + 命令传播</code>，并没有利用AOF文件，与MySQL的binlog不同。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>redis 3.0 源码</li>
<li>redis 设计与实现</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Redis</tag>
      </tags>
  </entry>
  <entry>
    <title>从windows资源管理器启动Python脚本</title>
    <url>/2018/02/04/run-python-from-explorer/</url>
    <content><![CDATA[<p>有时想给windows的资源管理器增加一些自定义的功能，比如创建目录联接到某个目录。</p>
<h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><p>给windows的资源管理器添加一个右键菜单，调用你想要运行的程序或脚本，理论上可以实现任意功能。</p>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p><strong>那么我们就开始py一个脚本吧, 创建目录联接到指定目录</strong></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> os, sys, subprocess</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(sys.argv) &lt; <span class="number">2</span>:</span><br><span class="line">    exit()</span><br><span class="line"></span><br><span class="line">p = sys.argv[<span class="number">1</span>]  <span class="comment"># 目标路径通过命令行参数传入</span></span><br><span class="line">base_dir, name = os.path.split(p)</span><br><span class="line"></span><br><span class="line">si = subprocess.STARTUPINFO()</span><br><span class="line">si.dwFlags |= subprocess.STARTF_USESHOWWINDOW   <span class="comment"># 隐藏subprocess运行的命令窗口</span></span><br><span class="line">cmd = <span class="string">&#x27;mklink /J &#123;&#125; &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(os.path.join(<span class="string">&#x27;E:\\share&#x27;</span>, name), p)</span><br><span class="line">subprocess.call(cmd, startupinfo=si, shell=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure>
<span id="more"></span>
<p><strong>修改注册表,添加右键菜单</strong></p>
<p>复制这段保存为 add_to_share.reg 双击执行导入</p>
<figure class="highlight nix"><table><tr><td class="code"><pre><span class="line">Windows Registry Editor Version <span class="number">5.00</span></span><br><span class="line"></span><br><span class="line">[HKEY_CLASSES_ROOT\Directory\shell\share]</span><br><span class="line">@<span class="operator">=</span><span class="string">&quot;添加到共享文件夹&quot;</span></span><br><span class="line"></span><br><span class="line">[HKEY_CLASSES_ROOT\Directory\shell\share\command]</span><br><span class="line">@<span class="operator">=</span><span class="string">&quot;<span class="char escape_">\&quot;</span>D:<span class="char escape_">\\</span>lib<span class="char escape_">\\</span>python3<span class="char escape_">\\</span>pythonw.exe<span class="char escape_">\&quot;</span> <span class="char escape_">\&quot;</span>D:<span class="char escape_">\\</span>Scripts<span class="char escape_">\\</span>link_to_share.py<span class="char escape_">\&quot;</span> <span class="char escape_">\&quot;</span>%1<span class="char escape_">\&quot;</span>&quot;</span></span><br><span class="line">; D:\\lib\\python3\\pythonw.exe 这是python解释器路径, pythonw.exe 执行时不会有解释器窗口</span><br><span class="line">; D:\\Scripts\\link_to_share.py 脚本路径</span><br><span class="line">; %<span class="number">1</span> 选中的文件夹全路径</span><br></pre></td></tr></table></figure>

<h3 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h3><p><img data-src="https://image.ponder.work/mweb/18-2-4/77007025.jpg"><br><img data-src="https://image.ponder.work/mweb/18-2-4/54072201.jpg"></p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>雪花算法原理及实现</title>
    <url>/2021/06/08/snowflake-python-edition/</url>
    <content><![CDATA[<p>分布式系统中，全局唯一id的生成是个常见的问题。在互联网的业务系统中，涉及到各种各样的ID，如在支付系统中就会有支付ID、退款ID等。</p>
<span id="more"></span>
<h2 id="常见ID生成方案"><a href="#常见ID生成方案" class="headerlink" title="常见ID生成方案"></a>常见ID生成方案</h2><h3 id="UUID"><a href="#UUID" class="headerlink" title="UUID"></a>UUID</h3><p>算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。</p>
<ul>
<li>优点：本地生成，生成简单，性能好，没有高可用风险</li>
<li>缺点：长度过长，存储冗余，且无序不可读，查询效率低</li>
</ul>
<h3 id="数据库自增ID"><a href="#数据库自增ID" class="headerlink" title="数据库自增ID"></a>数据库自增ID</h3><p>使用数据库的id自增策略，如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长，生成不重复ID的策略来实现高可用。</p>
<ul>
<li>优点：数据库生成的ID绝对有序，高可用实现方式简单</li>
<li>缺点：需要独立部署数据库实例，成本高，有性能瓶颈</li>
</ul>
<h3 id="Redis生成ID"><a href="#Redis生成ID" class="headerlink" title="Redis生成ID"></a>Redis生成ID</h3><p>Redis的所有命令操作都是单线程的，本身提供像 incr 和 increby 这样的自增原子命令，所以能保证生成的 ID 肯定是唯一有序的。</p>
<ul>
<li>优点：不依赖于数据库，灵活方便，且性能优于数据库；数字ID天然排序，对分页或者需要排序的结果很有帮助。</li>
<li>缺点：如果系统中没有Redis，还需要引入新的组件，增加系统复杂度；需要编码和配置的工作量比较大。</li>
</ul>
<h2 id="雪花算法原理"><a href="#雪花算法原理" class="headerlink" title="雪花算法原理"></a>雪花算法原理</h2><p>SnowFlake 算法，是 Twitter 开源的分布式 id 生成算法。</p>
<p>其核心思想就是：使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛，且ID 引入了时间戳，保持自增性且不重复。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-06-08-16231545423727.jpg"></p>
<p>主要分为 5 个部分：</p>
<ol>
<li>1 个 bit：0，无特殊意义，主要是为防止歧义，因为负数的第一位是1。</li>
<li>41 个 bit：表示的是时间戳，毫秒值，可表示范围0到2^41-1。</li>
<li>10 个 bit: 代表分区或者机器，可以表示范围0到1024</li>
<li>12 个 bit：表示分区内的自增序号，可表示范围0到4096</li>
</ol>
<p>所以理论上每秒可以生成<code>1000 * 1024 * 4096 = 4194304000</code>个id，完全足够使用</p>
<h2 id="Python-实现"><a href="#Python-实现" class="headerlink" title="Python 实现"></a>Python 实现</h2><p>有几个需要注意的点</p>
<ul>
<li>系统时间可能会不准，要防止时间戳回拨</li>
<li>分区的10bit也可以进行二次分组，如前2bit代表机房，后8个bit代表机器</li>
<li>时间戳不一定要存储实际的时间戳，可以存储相对某个时间的变化，更节省空间</li>
</ul>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> time <span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">MAX_WORKER = <span class="number">2</span> ** <span class="number">10</span> - <span class="number">1</span></span><br><span class="line">MAX_SEQ = <span class="number">2</span> ** <span class="number">12</span> - <span class="number">1</span></span><br><span class="line">TIME_OFFSET = <span class="number">1577808000000</span>  <span class="comment"># datetime.datetime(2020, 1, 1, 0, 0)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SnowFlake</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, worker_id</span>):</span><br><span class="line">        <span class="keyword">assert</span> <span class="number">0</span> &lt;= worker_id &lt; MAX_WORKER</span><br><span class="line">        <span class="variable language_">self</span>._worker_id = <span class="built_in">int</span>(worker_id)</span><br><span class="line">        <span class="variable language_">self</span>._timestamp = <span class="built_in">int</span>(time() * <span class="number">1000</span>)</span><br><span class="line">        <span class="variable language_">self</span>._seq = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_id</span>(<span class="params">self</span>):</span><br><span class="line">        res = <span class="number">0</span></span><br><span class="line">        res |= <span class="variable language_">self</span>._seq</span><br><span class="line">        res |= (<span class="variable language_">self</span>._worker_id &lt;&lt; <span class="number">12</span>)</span><br><span class="line">        res |= ((<span class="variable language_">self</span>._timestamp - TIME_OFFSET) &lt;&lt; <span class="number">22</span>)</span><br><span class="line">        <span class="keyword">return</span> res</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">next_id</span>(<span class="params">self</span>):</span><br><span class="line">        t = <span class="built_in">int</span>(time() * <span class="number">1000</span>)</span><br><span class="line">        <span class="keyword">assert</span> t &gt;= <span class="variable language_">self</span>._timestamp</span><br><span class="line">        <span class="keyword">assert</span> <span class="variable language_">self</span>._seq &lt; MAX_SEQ</span><br><span class="line">        <span class="keyword">if</span> t &gt; <span class="variable language_">self</span>._timestamp:</span><br><span class="line">            <span class="variable language_">self</span>._timestamp = t</span><br><span class="line">            <span class="variable language_">self</span>._seq = <span class="number">0</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="variable language_">self</span>._seq += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.get_id()</span><br><span class="line"></span><br><span class="line"><span class="meta">    @staticmethod</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_bin</span>(<span class="params">val</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;&#123;:0&gt;64&#125;&#x27;</span>.<span class="built_in">format</span>(<span class="built_in">bin</span>(val)[<span class="number">2</span>:])</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    sf = SnowFlake(<span class="number">1</span>)</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">100</span>):</span><br><span class="line">        <span class="built_in">print</span>(sf.get_bin(sf.next_id()), sf._timestamp, sf._seq)</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://juejin.cn/post/6844903631137800200">https://juejin.cn/post/6844903631137800200</a></li>
<li><a href="https://www.cnblogs.com/wuzhenzhao/p/13295382.html">https://www.cnblogs.com/wuzhenzhao/p/13295382.html</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>分布式</tag>
        <tag>算法</tag>
      </tags>
  </entry>
  <entry>
    <title>redroid “设备未获得play保护机制认证” 问题</title>
    <url>/2025/05/07/redroid-google-play-login/</url>
    <content><![CDATA[<p>使用 redroid 等安卓虚拟环境，可能会发现 google play 用不了的问题。<br>虽然系统集成了 gapps，但系统提示 “设备未获得play保护机制认证”，无法登录 play 商店。<br>可能的原因比较多，这里大概是因为虚拟机的型号没在google的数据库里。<br>解决方案就是，获取 GSF ID 注册到 Google。</p>
<span id="more"></span>
<p><img data-src="https://image.ponder.work/mweb/2025-05-08---17466861942743.jpg"></p>
<h2 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h2><ol>
<li>安装 <a href="https://apkpure.com/device-id/vtechnotm.com.deviceid">device id</a></li>
<li>打开应用，复制 GSF id，假设为<code>ffffffff</code></li>
<li>终端运行<code>printf &quot;%d\n&quot; 0xffffffff</code>，转换换成10进制数字。</li>
<li>打开 <a href="https://www.google.com/android/uncertified/">https://www.google.com/android/uncertified/</a> 输入转换后的结果并提交</li>
</ol>
<p><img data-src="https://image.ponder.work/mweb/2025-05-08---17466877549957.jpg">
 </p>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>Android</tag>
      </tags>
  </entry>
  <entry>
    <title>数独与回溯法</title>
    <url>/2021/07/10/sodoku-and-backtracking/</url>
    <content><![CDATA[<p>数独是一种数学逻辑游戏，游戏由9×9个格子组成，玩家需要根据格子提供的数字推理出其他格子的数字。游戏设计者会提供最少17个数字使得解答谜题只有一个答案。</p>
<p>数独的解法需 遵循如下规则：</p>
<ol>
<li>数字 1-9 在每一行只能出现一次。</li>
<li>数字 1-9 在每一列只能出现一次。</li>
<li>数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。</li>
</ol>
<span id="more"></span>
<p><img data-src="https://image.ponder.work/mweb/2021-07-11-16259930856620.jpg" alt="-w285"></p>
<p>虽然玩法简单，但提供的数字却千变万化，所以很适合用程序来求解。</p>
<p>类似这种需要穷举的问题一般采用回溯法，也就是暴力求解，在最坏的情况下时间复杂度为指数。</p>
<h2 id="回溯法"><a href="#回溯法" class="headerlink" title="回溯法"></a>回溯法</h2><p>回溯法采用试错的思想，它尝试分步的去解决一个问题。在解决问题的过程中，如果现有的分步答案不能得到有效的正确的解答的时候，它将取消上一步甚至是上几步的计算，再通过其它的可能的分步解答再次尝试寻找问题的答案。</p>
<p>回溯法通常用最简单的递归方法来实现，下面是回溯法的一般结构。<br>理解回溯法首先要理解递归，理解递归的过程，以及递归的返回值。可以通过查看斐波那契数列的递归实现的调用栈来理解递归的过程。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">backtracking</span>(<span class="params">args</span>):</span><br><span class="line">    <span class="keyword">if</span> can_stop(args):  <span class="comment"># 判断是否终止，限制了递归深度</span></span><br><span class="line">        <span class="keyword">if</span> need():   <span class="comment"># 必要时收集递归子树的叶子节点的结果</span></span><br><span class="line">            collect_result()</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> choice <span class="keyword">in</span> get_all_choices(args):  <span class="comment"># 针对每一种情况, 情况的个数也就是每一层的广度</span></span><br><span class="line">        set_state(args, choice)  <span class="comment"># 暂时选择该项</span></span><br><span class="line">        process(args)   <span class="comment"># 该节点数据加工</span></span><br><span class="line">        backtracking(args)   <span class="comment"># 递归进入下一层次</span></span><br><span class="line">        revert_state(args, choice)   <span class="comment"># 撤销当前选择</span></span><br></pre></td></tr></table></figure>

<h2 id="暴力解法"><a href="#暴力解法" class="headerlink" title="暴力解法"></a>暴力解法</h2><p>回到数独解法上来</p>
<p>首先看最简单的暴力解法</p>
<p>这里有几个点和模板不一样</p>
<ul>
<li>由于把整个棋盘都填满游戏就终止了，所以不需要<code>can_stop</code>判断</li>
<li>每层递归会填满一个空位，递归的最大深度就是空位的个数。</li>
<li>数独棋盘都填满才算一个解，所以每层递归<code>get_all_choices</code>最多有 <code>行 x 列 x 数字取值 = 9 x 9 x 9</code> 种情况</li>
<li>由于数独规则的约束，行、列、九宫格内数字不能重复，所以天然有部分剪枝</li>
</ul>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 0 代表空位</span></span><br><span class="line">board = [</span><br><span class="line">    [<span class="number">0</span>, <span class="number">8</span>, <span class="number">6</span>, <span class="number">9</span>, <span class="number">0</span>, <span class="number">2</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">    [<span class="number">0</span>, <span class="number">0</span>, <span class="number">7</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">    [<span class="number">5</span>, <span class="number">2</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">7</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">    [<span class="number">2</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">6</span>],</span><br><span class="line">    [<span class="number">0</span>, <span class="number">0</span>, <span class="number">5</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">    [<span class="number">0</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">6</span>, <span class="number">0</span>, <span class="number">8</span>, <span class="number">2</span>],</span><br><span class="line">    [<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">4</span>, <span class="number">0</span>, <span class="number">9</span>, <span class="number">6</span>, <span class="number">5</span>, <span class="number">0</span>],</span><br><span class="line">    [<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">2</span>, <span class="number">0</span>, <span class="number">3</span>],</span><br><span class="line">    [<span class="number">4</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">6</span>, <span class="number">0</span>, <span class="number">5</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">EMPTY = <span class="number">0</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sodoku</span>(<span class="params">board</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">can_put</span>(<span class="params">board, row, col, c</span>):</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>):</span><br><span class="line">            <span class="keyword">if</span> board[i][col] != EMPTY <span class="keyword">and</span> board[i][col] == c:  <span class="comment"># 行不冲突</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">            <span class="keyword">if</span> board[row][i] != EMPTY <span class="keyword">and</span> board[row][i] == c:  <span class="comment"># 列不冲突</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">            x, y = row // <span class="number">3</span> * <span class="number">3</span> + i // <span class="number">3</span>,  col // <span class="number">3</span> * <span class="number">3</span> + i % <span class="number">3</span></span><br><span class="line">            <span class="keyword">if</span> board[x][y] != EMPTY <span class="keyword">and</span> board[x][y] == c:  <span class="comment"># 九宫不冲突</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">solve</span>(<span class="params">board</span>):</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>):</span><br><span class="line">            <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>):</span><br><span class="line">                <span class="keyword">if</span> board[i][j] == EMPTY:</span><br><span class="line">                    <span class="keyword">for</span> c <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">10</span>):</span><br><span class="line">                        <span class="keyword">if</span> can_put(board, i, j, c):</span><br><span class="line">                            board[i][j] = c</span><br><span class="line">                            <span class="keyword">if</span> solve(board):</span><br><span class="line">                                <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">                            <span class="keyword">else</span>:</span><br><span class="line">                                board[i][j] = EMPTY</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">return</span> solve(board)</span><br></pre></td></tr></table></figure>

<h2 id="剪枝"><a href="#剪枝" class="headerlink" title="剪枝"></a>剪枝</h2><p>所谓的剪枝，就是递归每一层的时候，并不是所有情况都是有效的，可以跳过这些分支。</p>
<p><img data-src="https://image.ponder.work/mweb/2021-07-11-16260036519609.jpg" alt="-w351"></p>
<p>以该题为例，第一行第三列可选取值并不是1到9，而是<code>1，2，4</code>，而且随着我们不断把空位填满，越后面的点选择越少。<br>可以用集合存储每个空位的行、列、九宫方向的可选值，三者交集就是该点的可选值。（用位替换集合还可以进一步优化算法）<br>因为填充一个空位，会影响该位置行、列、九宫上的所有空位的取值，所有不直接存储该点的可选值。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">EMPTY = <span class="number">0</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sodoku</span>(<span class="params">board</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">solve</span>(<span class="params">board</span>):</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>):</span><br><span class="line">            <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>):</span><br><span class="line">                box_index = (i // <span class="number">3</span> ) * <span class="number">3</span> + j // <span class="number">3</span></span><br><span class="line">                <span class="keyword">if</span> board[i][j] == EMPTY:</span><br><span class="line">                    <span class="keyword">for</span> c <span class="keyword">in</span> (rows[i] &amp; columns[j] &amp; boxes[box_index]):    <span class="comment"># 通过集合来剪枝</span></span><br><span class="line">                        <span class="comment"># 设置状态</span></span><br><span class="line">                        board[i][j] = c</span><br><span class="line">                        tmp = [<span class="literal">False</span>, <span class="literal">False</span>, <span class="literal">False</span>]</span><br><span class="line">                        <span class="keyword">for</span> idx, elem <span class="keyword">in</span> <span class="built_in">enumerate</span>((rows[i], columns[j], boxes[box_index])):</span><br><span class="line">                            <span class="keyword">if</span> c <span class="keyword">in</span> elem:</span><br><span class="line">                                elem.remove(c)</span><br><span class="line">                                tmp[idx] = <span class="literal">True</span></span><br><span class="line">                        <span class="comment"># 进入下一层递归</span></span><br><span class="line">                        <span class="keyword">if</span> solve(board):</span><br><span class="line">                            <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">                        <span class="comment"># 还原状态</span></span><br><span class="line">                        board[i][j] = EMPTY</span><br><span class="line">                        <span class="keyword">for</span> idx, elem <span class="keyword">in</span> <span class="built_in">zip</span>(tmp, (rows[i], columns[j], boxes[box_index])):</span><br><span class="line">                            <span class="keyword">if</span> idx:</span><br><span class="line">                                elem.add(c)</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 验证题目是否合法, 初始化每一格可选择项</span></span><br><span class="line">    rows = [<span class="built_in">set</span>(<span class="built_in">range</span>(<span class="number">1</span>,<span class="number">10</span>)) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>)]  <span class="comment"># 行内所有点的可选值</span></span><br><span class="line">    columns = [<span class="built_in">set</span>(<span class="built_in">range</span>(<span class="number">1</span>,<span class="number">10</span>)) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>)]  <span class="comment"># 列</span></span><br><span class="line">    boxes = [<span class="built_in">set</span>(<span class="built_in">range</span>(<span class="number">1</span>,<span class="number">10</span>)) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>)]  <span class="comment"># 九宫</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>):</span><br><span class="line">        <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>):</span><br><span class="line">            num = board[i][j]</span><br><span class="line">            <span class="keyword">if</span> num != EMPTY:</span><br><span class="line">                num = <span class="built_in">int</span>(num)</span><br><span class="line">                box_index = (i // <span class="number">3</span> ) * <span class="number">3</span> + j // <span class="number">3</span></span><br><span class="line">                <span class="keyword">if</span> num <span class="keyword">not</span> <span class="keyword">in</span> rows[i]:</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">                rows[i].remove(num)</span><br><span class="line">                <span class="keyword">if</span> num <span class="keyword">not</span> <span class="keyword">in</span> columns[j]:</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">                columns[j].remove(num)</span><br><span class="line">                <span class="keyword">if</span> num <span class="keyword">not</span> <span class="keyword">in</span> boxes[box_index]:</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">                boxes[box_index].remove(num)</span><br><span class="line">    <span class="keyword">return</span> solve(board)</span><br></pre></td></tr></table></figure>

<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://zh.wikipedia.org/wiki/%E6%95%B8%E7%8D%A8">https://zh.wikipedia.org/wiki/%E6%95%B8%E7%8D%A8</a><br><a href="https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%BA%AF%E6%B3%95">https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%BA%AF%E6%B3%95</a><br><a href="https://www.jianshu.com/p/8e694d079a76">https://www.jianshu.com/p/8e694d079a76</a></p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>算法</tag>
        <tag>回溯法</tag>
      </tags>
  </entry>
  <entry>
    <title>SublimeText3 快捷键列表</title>
    <url>/2016/07/16/sublime%E5%BF%AB%E6%8D%B7%E9%94%AE/</url>
    <content><![CDATA[<p>主力使用SublimeText3编辑器，将一些常用的快捷键总结一下。</p>
<h2 id="通用（General）"><a href="#通用（General）" class="headerlink" title="通用（General）"></a>通用（General）</h2><ul>
<li><code>Esc</code> 退出功能，比如退出输出面板</li>
<li><code>↑↓←→</code>	上下左右移动光标，注意不是不是KJHL！</li>
<li><code>Alt</code>	显示菜单栏</li>
<li><code>Ctrl + Shift + P</code>	调出命令板（Command Palette）</li>
<li><code>Ctrl + ` </code>	调出控制台</li>
<li><code>Ctrl + K, Ctrl + 数字</code>	代码折叠，数字代表折叠的层次</li>
<li><code>Ctrl + Shift + [ / ]</code>	收起&#x2F;展开选中的代码。</li>
</ul>
<span id="more"></span>

<h2 id="窗口（Window）"><a href="#窗口（Window）" class="headerlink" title="窗口（Window）"></a>窗口（Window）</h2><ul>
<li><code>Ctrl + Shift + N</code>	创建一个新窗口</li>
<li><code>Ctrl + N</code>	在当前窗口创建一个新标签</li>
<li><code>Ctrl + W</code>	关闭当前标签</li>
<li><code>Ctrl + Shift + T</code>	恢复刚刚关闭的标签</li>
</ul>
<h2 id="屏幕（Screen）"><a href="#屏幕（Screen）" class="headerlink" title="屏幕（Screen）"></a>屏幕（Screen）</h2><ul>
<li><code>F11</code>	切换普通全屏</li>
<li><code>Shift + F11</code>	切换无干扰全屏</li>
<li><code>Alt + Shift + 2/3/4</code>	左右分组,分2到4组</li>
<li><code>Alt + Shift + 8/9</code>	上下分组,分2到3组</li>
<li><code>Alt + Shift + 5</code>	上下左右分组</li>
<li><code>Ctrl + 数字键</code>	跳转到指定组</li>
<li><code>Ctrl + Shift + 数字键</code>	将当前标签移动到指定组</li>
</ul>
<h2 id="跳转（Jumping）"><a href="#跳转（Jumping）" class="headerlink" title="跳转（Jumping）"></a>跳转（Jumping）</h2><ul>
<li><code>Ctrl + P</code>	跳转到指定文件，输入文件名后可以打开对应文件</li>
<li><code>@ 符号跳转</code>	输入@symbol跳转到symbol符号所在的位置，比如函数或类</li>
<li><code># 关键字跳转</code>	输入#keyword跳转到keyword所在的位置</li>
<li><code>: 行号跳转</code>	输入:12跳转到文件的第12行。</li>
<li><code>Ctrl + R</code>	跳转到指定符号 @</li>
<li><code>Ctrl + G</code>	跳转到指定行号 :</li>
<li><code>Ctrl + ;</code>	跳转到指定关键字</li>
</ul>
<h2 id="编辑（Editing）"><a href="#编辑（Editing）" class="headerlink" title="编辑（Editing）"></a>编辑（Editing）</h2><ul>
<li><code>Ctrl + /</code>	注释当前行，取消注释</li>
<li><code>Ctrl + Enter</code>	在当前行下面新增一行然后跳至该行</li>
<li><code>Ctrl + Shift + Enter</code>	在当前行上面增加一行并跳至该行</li>
<li><code>Ctrl + ←/→</code>	进行逐词左右移动</li>
<li><code>Ctrl + Shift + ←/→ </code>	进行左右逐词选择</li>
<li><code>Ctrl + ↑/↓ </code>	上下移动当前显示区域</li>
<li><code>Ctrl + Shift + ↑/↓ </code> 上下移动当前行，或者选中行</li>
<li><code>Ctrl + KU/KL </code>	转换当前单词为大写&#x2F;小写</li>
</ul>
<h2 id="选择（Selecting）"><a href="#选择（Selecting）" class="headerlink" title="选择（Selecting）"></a>选择（Selecting）</h2><ul>
<li><code>Ctrl + D</code>	选择当前词，进入多重编辑</li>
<li><code>Ctrl + K</code>	多重编辑时跳过当前选中的</li>
<li><code>Ctrl + U</code>	多重编辑，回退选中</li>
<li><code>Ctrl + Shift + L</code>	将当前选中区域打散</li>
<li><code>Ctrl + J</code>	把当前选中区域合并为一行</li>
<li><code>Ctrl + M</code>	在起始括号和结尾括号间切换</li>
<li><code>Ctrl + Shift + M</code>	快速选择括号间的内容</li>
<li><code>Ctrl + Shift + J</code>	快速选择同缩进的内容</li>
<li><code>Ctrl + Shift + Space</code>	快速选择当前作用域（Scope）的内容</li>
</ul>
<h2 id="查找-替换（Finding-Replacing）"><a href="#查找-替换（Finding-Replacing）" class="headerlink" title="查找&amp;替换（Finding&amp;Replacing）"></a>查找&amp;替换（Finding&amp;Replacing）</h2><ul>
<li><code>F3</code>	跳至当前关键字下一个位置</li>
<li><code>Shift + F3</code>	跳到当前关键字上一个位置</li>
<li><code>Alt + F3</code>	选中当前关键字出现的所有位置</li>
<li><code>Ctrl + F/H</code>	进行标准查找&#x2F;替换，之后</li>
<li><code>Alt + C</code>	切换大小写敏感（Case-sensitive）模式</li>
<li><code>Alt + W</code>	切换整字匹配（Whole matching）模式</li>
<li><code>Alt + R</code>	切换正则匹配（Regex matching）模式</li>
<li><code>Ctrl + Shift + H</code>	替换当前关键字</li>
<li><code>Ctrl + Alt + Enter</code>	替换所有关键字匹配</li>
<li><code>Ctrl + Shift + F</code>	多文件搜索&amp;替换</li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>编辑器</tag>
      </tags>
  </entry>
  <entry>
    <title>OpenBLAS blas_thread_init pthread_creat Resource temporarily unavailable 问题分析与解决</title>
    <url>/2020/02/28/solution-of-OpenBLAS-blas_thread_init-problem/</url>
    <content><![CDATA[<h2 id="症状"><a href="#症状" class="headerlink" title="症状"></a>症状</h2><p>最近在在一台服务器上发现, 一个服务的工作进程会异常退出, 但部署有相同代码的其他服务却没有类似的情况.</p>
<p>查看日志发现以下错误</p>
<figure class="highlight arduino"><table><tr><td class="code"><pre><span class="line"><span class="built_in">Traceback</span> (most recent call last):</span><br><span class="line">	...</span><br><span class="line">  <span class="built_in">File</span> <span class="string">&quot;/home/q/hawkeye_mid_dev/src/model/b_card_model.py&quot;</span>, line <span class="number">5</span>, in &lt;<span class="keyword">module</span>&gt;</span><br><span class="line">    <span class="keyword">import</span> numpy as np</span><br><span class="line">  <span class="built_in">File</span> <span class="string">&quot;/home/sync360/miniconda3/envs/xd_mid/lib/python2.7/site-packages/numpy/__init__.py&quot;</span>, line <span class="number">142</span>, in &lt;<span class="keyword">module</span>&gt;</span><br><span class="line">    from . <span class="keyword">import</span> add_newdocs</span><br><span class="line">  <span class="built_in">File</span> <span class="string">&quot;/home/sync360/miniconda3/envs/xd_mid/lib/python2.7/site-packages/numpy/add_newdocs.py&quot;</span>, line <span class="number">13</span>, in &lt;<span class="keyword">module</span>&gt;</span><br><span class="line">    from numpy.lib <span class="keyword">import</span> add_newdoc</span><br><span class="line">  <span class="built_in">File</span> <span class="string">&quot;/home/sync360/miniconda3/envs/xd_mid/lib/python2.7/site-packages/numpy/lib/__init__.py&quot;</span>, line <span class="number">8</span>, in &lt;<span class="keyword">module</span>&gt;</span><br><span class="line">    from .type_check <span class="keyword">import</span> *</span><br><span class="line">  <span class="built_in">File</span> <span class="string">&quot;/home/sync360/miniconda3/envs/xd_mid/lib/python2.7/site-packages/numpy/lib/type_check.py&quot;</span>, line <span class="number">11</span>, in &lt;<span class="keyword">module</span>&gt;</span><br><span class="line">    <span class="keyword">import</span> numpy.core.numeric as _nx</span><br><span class="line">  <span class="built_in">File</span> <span class="string">&quot;/home/sync360/miniconda3/envs/xd_mid/lib/python2.7/site-packages/numpy/core/__init__.py&quot;</span>, line <span class="number">16</span>, in &lt;<span class="keyword">module</span>&gt;</span><br><span class="line">    from . <span class="keyword">import</span> multiarray</span><br><span class="line">KeyboardInterrupt</span><br><span class="line">OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable</span><br><span class="line">OpenBLAS blas_thread_init: RLIMIT_NPROC <span class="number">1024</span> current, <span class="number">516033</span> max</span><br></pre></td></tr></table></figure>

<h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><h3 id="Resouce-limit"><a href="#Resouce-limit" class="headerlink" title="Resouce limit"></a>Resouce limit</h3><p>在上面的错误输出里有一个关键词 <code>RLIMIT_NPROC</code>, 涉及到了linux的Resouce limit.</p>
<p>在Linux系统中，Resouce limit指在一个进程的执行过程中，它所能得到的资源的限制，比如进程的core file的最大值，虚拟内存的最大值等。</p>
<span id="more"></span>

<p>Resouce limit的大小可以直接影响进程的执行状况。其有两个最重要的概念：soft limit 和 hard limit。</p>
<p>softlimit是指内核所能支持的资源上限, hard limit在资源中只是作为softlimit的上限。当你设置hard limit后，你以后设置的softlimit只能小于hard limit。</p>
<p>要说明的是，hardlimit只针对非特权进程，也就是进程的有效用户ID(effective user ID)不是0的进程。具有特权级别的进程(具有属性CAP_SYS_RESOURCE)，softlimit则只有内核上限。</p>
<blockquote>
<p>resource：可能的选择有</p>
<p>RLIMIT_AS 	进程的最大虚内存空间，字节为单位。<br>RLIMIT_CORE 	内核转存文件的最大长度。<br>RLIMIT_CPU 	最大允许的CPU使用时间，秒为单位。当进程达到软限制，内核将给其发送SIGXCPU信号，这一信号的默认行为是终止进程的执行。然而，可以捕捉信号，处理句柄可将控制返回给主程序。如果进程继续耗费CPU时间，核心会以每秒一次的频率给其发送SIGXCPU信号，直到达到硬限制，那时将给进程发送 SIGKILL信号终止其执行。<br>RLIMIT_DATA 	进程数据段的最大值。<br>RLIMIT_FSIZE 	进程可建立的文件的最大长度。如果进程试图超出这一限制时，核心会给其发送SIGXFSZ信号，默认情况下将终止进程的执行。<br>RLIMIT_LOCKS 	进程可建立的锁和租赁的最大值。<br>RLIMIT_MEMLOCK 	进程可锁定在内存中的最大数据量，字节为单位。<br>RLIMIT_MSGQUEUE 	进程可为POSIX消息队列分配的最大字节数。<br>RLIMIT_NICE 	进程可通过setpriority() 或 nice()调用设置的最大完美值。<br>RLIMIT_NOFILE 	指定比进程可打开的最大文件描述词大一的值，超出此值，将会产生EMFILE错误。<br><strong>RLIMIT_NPROC 	用户可拥有的最大进程数。</strong><br>RLIMIT_RTPRIO 	进程可通过sched_setscheduler 和 sched_setparam设置的最大实时优先级。<br>RLIMIT_SIGPENDING 	用户可拥有的最大挂起信号数。<br>RLIMIT_STACK 	最大的进程堆栈，以字节为单位。</p>
</blockquote>
<h3 id="用户可拥有的最大进程数"><a href="#用户可拥有的最大进程数" class="headerlink" title="用户可拥有的最大进程数"></a>用户可拥有的最大进程数</h3><p>可以看到<code>RLIMIT_NPROC</code>是指的	是用户可拥有的最大进程数.</p>
<p>通过读取<code>/proc/${PID}/limits</code>文件, 可以得知对应进程的Resouce limit数值</p>
<figure class="highlight tcl"><table><tr><td class="code"><pre><span class="line">$ sudo cat /<span class="keyword">proc</span>/13839/limits<span class="title"> </span></span><br><span class="line"><span class="title"></span></span><br><span class="line"><span class="title">Limit</span> <span class="title">                    Soft</span> Limit<span class="title">           Hard</span> Limit<span class="title">           Units</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> cpu<span class="title"> time</span> <span class="title">             unlimited</span> <span class="title">           unlimited</span> <span class="title">           seconds</span> <span class="title">  </span></span><br><span class="line"><span class="title">Max</span> file<span class="title"> size</span> <span class="title">            unlimited</span> <span class="title">           unlimited</span> <span class="title">           bytes</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> data<span class="title"> size</span> <span class="title">            unlimited</span> <span class="title">           unlimited</span> <span class="title">           bytes</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> stack<span class="title"> size</span>            10485760<span class="title">             unlimited</span> <span class="title">           bytes</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> core<span class="title"> file</span> size        0<span class="title">                    unlimited</span> <span class="title">           bytes</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> resident<span class="title"> set</span> <span class="title">         unlimited</span> <span class="title">           unlimited</span> <span class="title">           bytes</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> processes             1024                 516033<span class="title">               processes</span> <span class="title"></span></span><br><span class="line"><span class="title">Max</span> open<span class="title"> files</span>            32768                32768<span class="title">                files</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> locked<span class="title"> memory</span>         65536                65536<span class="title">                bytes</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> address<span class="title"> space</span> <span class="title">        unlimited</span> <span class="title">           unlimited</span> <span class="title">           bytes</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> file<span class="title"> locks</span> <span class="title">           unlimited</span> <span class="title">           unlimited</span> <span class="title">           locks</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> pending<span class="title"> signals</span>       516033               516033<span class="title">               signals</span> <span class="title">  </span></span><br><span class="line"><span class="title">Max</span> msgqueue<span class="title"> size</span>         819200               819200<span class="title">               bytes</span> <span class="title">    </span></span><br><span class="line"><span class="title">Max</span> nice<span class="title"> priority</span>         0                    0<span class="title">                    </span></span><br><span class="line"><span class="title">Max</span> realtime<span class="title"> priority</span>     0                    0<span class="title">                    </span></span><br><span class="line"><span class="title">Max</span> realtime<span class="title"> timeout</span> <span class="title">     unlimited</span> <span class="title">           unlimited</span> <span class="title">           us</span>    </span><br></pre></td></tr></table></figure>

<p>可以看到, <code>Max processes</code>一项的Soft Limit为1024, Hard Limit为516033, 与错误输出中的信息一致.</p>
<p>通过<code>ps -ef -T |grep $(whoami) | wc -l</code>可以查得当前用户的总进程(线程)数目, 结果是<code>1016</code>.</p>
<p>显然1016已结接近Soft Limit了, 当程序尝试启动更多进程时就回出错, 所以我们需要增大Soft Limit.</p>
<h3 id="增大RLIMIT-NPROC数值"><a href="#增大RLIMIT-NPROC数值" class="headerlink" title="增大RLIMIT_NPROC数值"></a>增大RLIMIT_NPROC数值</h3><p>通过修改<code>/etc/security/limits.d/90-nproc.conf</code>配置文件</p>
<p>增加以下内容, 将RLIMIT_NPROC设置为10240</p>
<figure class="highlight asciidoc"><table><tr><td class="code"><pre><span class="line"><span class="bullet">*    </span>soft    nproc    10240</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>执行<code>ulimit -u</code>, 确认已经生效</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ulimit</span> -u</span></span><br><span class="line">10240</span><br></pre></td></tr></table></figure>

<h3 id="重启进程问题依然存在"><a href="#重启进程问题依然存在" class="headerlink" title="重启进程问题依然存在"></a>重启进程问题依然存在</h3><p>但是问题依然存在, 不能解决. </p>
<p>再次查看进程的<code>RLIMIT_NPROC</code>, 发现并没有变化.</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cat</span> /proc/13839/limits   |grep <span class="string">&#x27;Max processes&#x27;</span></span></span><br><span class="line">Max processes             1024                 516033               processes</span><br></pre></td></tr></table></figure>

<p>但是从终端启动新服务, 其<code>RLIMIT_NPROC</code>数值已经是10240了. </p>
<p>由此初步判断, 应该是修改limits配置文件, 只对新启动的进程生效.</p>
<p>由于工作进程都是由supervisor托管的, 查看了supervisord进程的<code>RLIMIT_NPROC</code>, 发现也是1024.<br>由supervisord启动的所有进程的父进程都是supervisord进程, 都继承了它的<code>RLIMIT_NPROC</code>数值.</p>
<p>最终, 将supervisord进程重启, 问题得到解决.</p>
<h2 id="为什么1024的进程数不够用呢"><a href="#为什么1024的进程数不够用呢" class="headerlink" title="为什么1024的进程数不够用呢?"></a>为什么1024的进程数不够用呢?</h2><p>这个服务是个多进程的tornado服务, 共10个工作进程. 但是最终用了241个进程&#x2F;线程(Linux进程和线程某种意义上是等价的).</p>
<figure class="highlight coq"><table><tr><td class="code"><pre><span class="line">$ ps -ef -T |<span class="type">grep</span> sync360  | <span class="type">grep</span> mid  | <span class="type">wc</span> -l   </span><br><span class="line"><span class="number">241</span>  </span><br></pre></td></tr></table></figure>

<p>这台服务器上类似的服务又有三四个, 1024的量很快就用完了.</p>
<p>所以问题又回到了<code>OpenBLAS</code>身上</p>
<h3 id="OpenBLAS"><a href="#OpenBLAS" class="headerlink" title="OpenBLAS"></a>OpenBLAS</h3><p>OpenBLAS是高度优化的线性代数库, 很多机器学习的库都依赖了OpenBLAS.</p>
<p>OpenBLAS通过多线程的方式来加速计算, 所以241个进程很很好解释了.<br>由于我的服务器是24核心的, 一般多线程程序的默认线程数与cpu核心数一致.<br>所以: <code>241 = 10 * 24 + 1</code> </p>
<h3 id="减少使用的进程数"><a href="#减少使用的进程数" class="headerlink" title="减少使用的进程数"></a>减少使用的进程数</h3><p>因为服务本身已经是多进程的, 再启用OpenBLAS的多线程, 加速的意义其实不大了.</p>
<p><em>查阅资料后发现, 如果应用是多线程的，将会与多线程下的OpenBLAS发生冲突。因此OpenBLAS只能运行在单线程下。(未充分验证)</em></p>
<p>可以通过设置环境变量<code>OPENBLAS_NUM_THREADS</code>, 调整OpenBLAS的线程数.</p>
<p>设置之后, 重启程序, 出现另一个类似的问题</p>
<figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">libgomp:</span> Thread creation <span class="params">failed:</span> Resource temporarily unavailable</span><br></pre></td></tr></table></figure>

<p>这里的libgomp是OpenMP的动态库, OpenMP会被用来优化编译OpenBLAS.<br>所以我们将环境变量<code>OMP_NUM_THREADS</code>也设置为1</p>
<p>在python启动文件前面加入以下语句</p>
<figure class="highlight moonscript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="built_in">os</span></span><br><span class="line"><span class="built_in">os</span>.environ[<span class="string">&#x27;OPENBLAS_NUM_THREADS&#x27;</span>] = <span class="string">&#x27;1&#x27;</span> </span><br><span class="line"><span class="built_in">os</span>.environ[<span class="string">&#x27;OMP_NUM_THREADS&#x27;</span>] = <span class="string">&#x27;1&#x27;</span></span><br></pre></td></tr></table></figure>

<p>重启程序, 发现只使用了11个进程, 与预期相符合, 问题解决.</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>该问题的根本原因是最大进程数<code>RLIMIT_NPROC</code>不够用.</p>
<p>所以解决方法有两个</p>
<h3 id="增大最大进程数"><a href="#增大最大进程数" class="headerlink" title="增大最大进程数"></a>增大最大进程数</h3><p>通过修改<code>/etc/security/limits.d/90-nproc.conf</code>配置文件, 增大最大进程数</p>
<p>同时注意, 修改最大进程数等limits配置文件, 只对新启动的进程生效. 需要将相关进程都重启</p>
<h3 id="减小当前使用的进程"><a href="#减小当前使用的进程" class="headerlink" title="减小当前使用的进程"></a>减小当前使用的进程</h3><p>通过关闭多线程, 来减少占用的进程数.</p>
<p>设置环境变量<code>OPENBLAS_NUM_THREADS</code>和<code>OMP_NUM_THREADS</code>的值为1</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://github.com/numpy/numpy/issues/14868#issuecomment-552561001">https://github.com/numpy/numpy/issues/14868#issuecomment-552561001</a></li>
<li><a href="https://stackoverflow.com/questions/51256738/multiple-instances-of-python-running-simultaneously-limited-to-35#answer-51257384">https://stackoverflow.com/questions/51256738/multiple-instances-of-python-running-simultaneously-limited-to-35#answer-51257384</a></li>
<li><a href="https://stackoverflow.com/questions/52026652/openblas-blas-thread-init-pthread-create-resource-temporarily-unavailable#answer-57549064">https://stackoverflow.com/questions/52026652/openblas-blas-thread-init-pthread-create-resource-temporarily-unavailable#answer-57549064</a></li>
<li><a href="https://www.tecmint.com/set-limits-on-user-processes-using-ulimit-in-linux/">https://www.tecmint.com/set-limits-on-user-processes-using-ulimit-in-linux/</a></li>
<li><a href="https://blog.csdn.net/hellochenlu/article/details/51604007">https://blog.csdn.net/hellochenlu/article/details/51604007</a></li>
<li><a href="https://blog.csdn.net/iteye_10725/article/details/82452978">https://blog.csdn.net/iteye_10725/article/details/82452978</a></li>
<li><a href="https://blog.csdn.net/dingding_tao/article/details/81043304">https://blog.csdn.net/dingding_tao/article/details/81043304</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>机器学习</tag>
      </tags>
  </entry>
  <entry>
    <title>全新出发</title>
    <url>/2019/09/19/start-again/</url>
    <content><![CDATA[<p>博客荒废了许久，换个主题，又是一个新的开始。。。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
  </entry>
  <entry>
    <title>SQLAlchemy Core 使用教程</title>
    <url>/2019/10/28/the-guide-of-SQLAlchemy-core/</url>
    <content><![CDATA[<p>SQLAlchemy core 是SQLAlchemy的核心部件，主要负责生成sql查询和具体的数据库操作，SQLAlchemy orm 就是构建在core之上的。</p>
<p>在不需要对象映射的时候，使用core而不是orm，可以降低数据库操作成本，提高性能。</p>
<h2 id="Table映射"><a href="#Table映射" class="headerlink" title="Table映射"></a>Table映射</h2><h3 id="创建数据库连接"><a href="#创建数据库连接" class="headerlink" title="创建数据库连接"></a>创建数据库连接</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> create_engine</span><br><span class="line">engine = create_engine(<span class="string">&#x27;sqlite:///test.sqlite3&#x27;</span>, echo=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure>

<h3 id="表结构定义"><a href="#表结构定义" class="headerlink" title="表结构定义"></a>表结构定义</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> Table, Column, Integer, String, MetaData, ForeignKey, Index</span><br><span class="line">metadata = MetaData()</span><br><span class="line">users = Table(<span class="string">&#x27;users&#x27;</span>, metadata,</span><br><span class="line">    Column(<span class="string">&#x27;id&#x27;</span>, Integer, primary_key=<span class="literal">True</span>),</span><br><span class="line">    Column(<span class="string">&#x27;name&#x27;</span>, String),</span><br><span class="line">    Column(<span class="string">&#x27;fullname&#x27;</span>, String),</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># place an index on col1, col2</span></span><br><span class="line">    Index(<span class="string">&#x27;idx_col12&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;fullname&#x27;</span>),   <span class="comment"># 添加索引</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">addresses = Table(<span class="string">&#x27;addresses&#x27;</span>, metadata,</span><br><span class="line">    Column(<span class="string">&#x27;id&#x27;</span>, Integer, primary_key=<span class="literal">True</span>),</span><br><span class="line">    Column(<span class="string">&#x27;user_id&#x27;</span>, <span class="literal">None</span>, ForeignKey(<span class="string">&#x27;users.id&#x27;</span>)),</span><br><span class="line">    Column(<span class="string">&#x27;email_address&#x27;</span>, String, nullable=<span class="literal">False</span>)</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<span id="more"></span>

<h3 id="映射已存在的数据表"><a href="#映射已存在的数据表" class="headerlink" title="映射已存在的数据表"></a>映射已存在的数据表</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 单个表</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> Table , MetaData</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>metadata = MetaData(engine)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>users = Table(<span class="string">&#x27;users&#x27;</span>, metadata, autoload=<span class="literal">True</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>users.c.keys()</span><br><span class="line">[<span class="string">&#x27;id&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;fullname&#x27;</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 整个数据库</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>metadata.reflect()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>addresses = metadata.tables[<span class="string">&#x27;addresses&#x27;</span>]</span><br></pre></td></tr></table></figure>

<h3 id="创建表"><a href="#创建表" class="headerlink" title="创建表"></a>创建表</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> MetaData</span><br><span class="line"></span><br><span class="line">metadata = MetaData()</span><br><span class="line">metadata.create_all(engine)</span><br></pre></td></tr></table></figure>

<h2 id="增删查改"><a href="#增删查改" class="headerlink" title="增删查改"></a>增删查改</h2><h3 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">## 获得连接对象</span></span><br><span class="line">conn = engine.connect()</span><br><span class="line"></span><br><span class="line"><span class="comment">## 插入一条</span></span><br><span class="line">ins = users.insert().values(name=<span class="string">&#x27;jack&#x27;</span>, fullname=<span class="string">&#x27;Jack Jones&#x27;</span>)</span><br><span class="line">conn.execute(ins)</span><br><span class="line"></span><br><span class="line">ins = users.insert()</span><br><span class="line">conn.execute(ins, name=<span class="string">&#x27;wendy&#x27;</span>, fullname=<span class="string">&#x27;Wendy Williams&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">## 批量插入</span></span><br><span class="line">conn.execute(addresses.insert(), [</span><br><span class="line">    &#123;<span class="string">&#x27;user_id&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;email_address&#x27;</span> : <span class="string">&#x27;jack@yahoo.com&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&#x27;user_id&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;email_address&#x27;</span> : <span class="string">&#x27;jack@msn.com&#x27;</span>&#125;,</span><br><span class="line">])</span><br></pre></td></tr></table></figure>

<h3 id="查询"><a href="#查询" class="headerlink" title="查询"></a>查询</h3><p>基本查询示例，复杂过滤条件查看<a href="https://docs.sqlalchemy.org/en/13/core/tutorial.html#operators">文档</a></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># select * from users</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> sqlalchemy.sql <span class="keyword">import</span> select</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cur = conn.execute(select([users]))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cur.fetchall()</span><br><span class="line">[(<span class="number">1</span>, <span class="string">&#x27;jack&#x27;</span>, <span class="string">&#x27;Jack Jones&#x27;</span>), (<span class="number">2</span>, <span class="string">&#x27;wendy&#x27;</span>, <span class="string">&#x27;Wendy Williams&#x27;</span>)]</span><br><span class="line"></span><br><span class="line"><span class="comment"># select name, fullname from users</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cur = conn.execute(select([users.c.name, users.c.fullname]))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cur.fetchall()</span><br><span class="line">[(<span class="string">&#x27;jack&#x27;</span>, <span class="string">&#x27;Jack Jones&#x27;</span>), (<span class="string">&#x27;wendy&#x27;</span>, <span class="string">&#x27;Wendy Williams&#x27;</span>)]</span><br><span class="line"></span><br><span class="line"><span class="comment"># select name, fullname from users limit 1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cur = conn.execute(select([users.c.name, users.c.fullname]).limit(<span class="number">1</span>))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cur.fetchall()</span><br><span class="line">[(<span class="string">&#x27;jack&#x27;</span>, <span class="string">&#x27;Jack Jones&#x27;</span>)]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 联表查询</span></span><br><span class="line"><span class="comment"># SELECT users.name, addresses.email_address FROM users, addresses WHERE users.id = addresses.user_id</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>s = select([users.c.name, addresses.c.email_address]).where(users.c.<span class="built_in">id</span> == addresses.c.user_id)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cur = conn.execute(s)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>cur.fetchall()</span><br><span class="line">[(<span class="string">&#x27;jack&#x27;</span>, <span class="string">&#x27;jack@yahoo.com&#x27;</span>), (<span class="string">&#x27;jack&#x27;</span>, <span class="string">&#x27;jack@msn.com&#x27;</span>)]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 原始sql语句</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> sqlalchemy.sql <span class="keyword">import</span> text</span><br><span class="line"><span class="meta">... </span>s = text(</span><br><span class="line"><span class="meta">... </span>    <span class="string">&quot;SELECT users.fullname, addresses.email_address AS title &quot;</span></span><br><span class="line"><span class="meta">... </span>        <span class="string">&quot;FROM users, addresses &quot;</span></span><br><span class="line"><span class="meta">... </span>        <span class="string">&quot;WHERE users.id = addresses.user_id &quot;</span></span><br><span class="line"><span class="meta">... </span>        <span class="string">&quot;AND users.name BETWEEN :x AND :y &quot;</span></span><br><span class="line"><span class="meta">... </span>        <span class="string">&quot;AND (addresses.email_address LIKE :e1 &quot;</span></span><br><span class="line"><span class="meta">... </span>            <span class="string">&quot;OR addresses.email_address LIKE :e2)&quot;</span>)</span><br><span class="line"><span class="meta">... </span>conn.execute(s, x=<span class="string">&#x27;m&#x27;</span>, y=<span class="string">&#x27;z&#x27;</span>, e1=<span class="string">&#x27;%@aol.com&#x27;</span>, e2=<span class="string">&#x27;%@msn.com&#x27;</span>).fetchall()</span><br><span class="line"></span><br><span class="line"><span class="comment">## 遍历大表，每N条数据请求数据库一次</span></span><br><span class="line"><span class="keyword">for</span> p <span class="keyword">in</span> conn.execute(select([users])).yield_per(<span class="number">5</span>):</span><br><span class="line">    <span class="built_in">print</span>(p)</span><br></pre></td></tr></table></figure>

<h3 id="更改"><a href="#更改" class="headerlink" title="更改"></a>更改</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> sqlalchemy.sql <span class="keyword">import</span> update, select, delete, insert</span><br><span class="line"><span class="comment"># 更改为具体值</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>stmt = users.update().\</span><br><span class="line"><span class="meta">... </span>            where(users.c.name == <span class="string">&#x27;jack&#x27;</span>).\</span><br><span class="line"><span class="meta">... </span>            values(name=<span class="string">&#x27;ed&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>conn.execute(stmt)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 更改为另一个字段的值</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>stmt = users.update().values(name=users.c.fullname)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>conn.execute(stmt)</span><br></pre></td></tr></table></figure>

<h3 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>stmt = users.delete().where(users.c.<span class="built_in">id</span> == <span class="number">1</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>conn.execute(stmt)</span><br></pre></td></tr></table></figure>

<h2 id="其他特性"><a href="#其他特性" class="headerlink" title="其他特性"></a>其他特性</h2><h3 id="监听DBAPI事件"><a href="#监听DBAPI事件" class="headerlink" title="监听DBAPI事件"></a>监听DBAPI事件</h3><p>监听connect事件，在初始化数据库连接时设置数据库参数变量，或者执行语句<br><a href="https://docs.sqlalchemy.org/en/13/core/engines.html#modifying-the-dbapi-connection-after-connect-or-running-commands-after-connect">参考文档</a></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> event</span><br><span class="line"></span><br><span class="line">engine = create_engine(</span><br><span class="line">    <span class="string">&quot;postgresql://user:pass@hostname/dbname&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="meta">@event.listens_for(<span class="params">engine, <span class="string">&quot;connect&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">connect</span>(<span class="params">dbapi_connection, connection_record</span>):</span><br><span class="line">    cursor = dbapi_connection.cursor()</span><br><span class="line">    cursor.execute(<span class="string">&quot;SET some session variables&quot;</span>)</span><br><span class="line">    cursor.close()</span><br></pre></td></tr></table></figure>]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>SQLAlchemy</tag>
        <tag>ORM</tag>
      </tags>
  </entry>
  <entry>
    <title>SQLAlchemy ORM 使用教程</title>
    <url>/2019/10/26/the-guide-of-SQLAlchemy-orm/</url>
    <content><![CDATA[<p>ORM是指对象关系映射（英语：Object Relational Mapping），是一种程序设计技术，是数据库记录和程序对象之间的映射关系。</p>
<p>使用ORM可以简化数据库的操作，使数据操作更加面向对象，并且程序逻辑和具体数据库解耦。缺点是会有一定的性能损耗。</p>
<p>Python中的ORM主要有Django ORM，SQLAlchemy, peewee； 其中Django ORM只能和Django框架一起使用，SQLAlchemy功能比较全，peewee较为轻量。</p>
<p>SQLAlchemy还可以不使用其ORM，只使用SQLAlchemy core作为一个通用数据库连接器。</p>
<span id="more"></span>

<h2 id="关系映射"><a href="#关系映射" class="headerlink" title="关系映射"></a>关系映射</h2><h3 id="创建model"><a href="#创建model" class="headerlink" title="创建model"></a>创建model</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> Column, Integer, String, DateTime, TIMESTAMP, text</span><br><span class="line"><span class="keyword">from</span> sqlalchemy.ext.declarative <span class="keyword">import</span> declarative_base</span><br><span class="line"></span><br><span class="line">Base = declarative_base()</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SomeData</span>(<span class="title class_ inherited__">Base</span>):</span><br><span class="line">    __tablename__ = <span class="string">&#x27;table11&#x27;</span></span><br><span class="line">    __table_args__ = (</span><br><span class="line">        Index(<span class="string">&#x27;message_idx&#x27;</span>, <span class="string">&#x27;message&#x27;</span>),</span><br><span class="line">    )</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">id</span> = Column(Integer, primary_key=<span class="literal">True</span>)</span><br><span class="line">    status = Column(<span class="string">&#x27;status&#x27;</span>, String(<span class="number">4</span>) , comment=<span class="string">&#x27;状态&#x27;</span>, quote=<span class="literal">True</span>)   <span class="comment"># 转义关键字</span></span><br><span class="line">    message = Column(String(<span class="number">50</span>), comment=<span class="string">&#x27;描述&#x27;</span>,)</span><br><span class="line">    the_time = Column(DateTime, comment=<span class="string">&#x27;请求时间&#x27;</span>, index=<span class="literal">True</span>)</span><br><span class="line">    cost_time = Column(String(<span class="number">8</span>), comment=<span class="string">&#x27;请求耗时&#x27;</span>,)</span><br><span class="line">    <span class="comment"># created_at = Column(TIMESTAMP, comment=&#x27;创建时间&#x27;, server_default=text(&#x27;CURRENT_TIMESTAMP&#x27;))</span></span><br></pre></td></tr></table></figure>

<h3 id="数据库URI"><a href="#数据库URI" class="headerlink" title="数据库URI"></a>数据库URI</h3><p>SQLAlchemy使用类似<code>sqlite:///test.sqlite3</code>的URI来表示数据库连接<br>格式为：<code>dialect+driver://username:password@host:port/database</code>, 具体查看<a href="https://docs.sqlalchemy.org/en/13/core/engines.html#database-urls">官方文档</a></p>
<h3 id="生成Model对应数据表"><a href="#生成Model对应数据表" class="headerlink" title="生成Model对应数据表"></a>生成Model对应数据表</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> create_engine</span><br><span class="line"><span class="keyword">from</span> sqlalchemy.orm <span class="keyword">import</span> sessionmaker</span><br><span class="line"></span><br><span class="line">engine = create_engine(<span class="string">&#x27;sqlite:///test.sqlite3&#x27;</span>)</span><br><span class="line">Base.metadata.create_all(engine)  <span class="comment"># 如果表不存在，则生成</span></span><br></pre></td></tr></table></figure>

<h3 id="自动映射已存在的数据表"><a href="#自动映射已存在的数据表" class="headerlink" title="自动映射已存在的数据表"></a>自动映射已存在的数据表</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> create_engine</span><br><span class="line"><span class="keyword">from</span> sqlalchemy.orm <span class="keyword">import</span> Session</span><br><span class="line"><span class="keyword">from</span> sqlalchemy.ext.automap <span class="keyword">import</span> automap_base</span><br><span class="line"></span><br><span class="line">Base = automap_base()</span><br><span class="line"></span><br><span class="line">dbname = <span class="string">&#x27;test.sqlite3&#x27;</span></span><br><span class="line">engine = create_engine(<span class="string">&#x27;sqlite:///&#x27;</span> + dbname)</span><br><span class="line"></span><br><span class="line">Base.prepare(engine, reflect=<span class="literal">True</span>)</span><br><span class="line"><span class="comment"># 只映射部分表</span></span><br><span class="line"><span class="comment"># Base.prepare(engine, reflect=True, reflection_options=&#123;&#x27;only&#x27;: [&#x27;table11&#x27;]&#125;)</span></span><br><span class="line">Base.classes.keys()  <span class="comment"># 所有映射列表</span></span><br><span class="line"></span><br><span class="line">SomeData = Base.classes.table11</span><br></pre></td></tr></table></figure>

<h2 id="增删查改"><a href="#增删查改" class="headerlink" title="增删查改"></a>增删查改</h2><h3 id="创建会话session"><a href="#创建会话session" class="headerlink" title="创建会话session"></a>创建会话session</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> create_engine</span><br><span class="line"><span class="keyword">from</span> sqlalchemy.orm <span class="keyword">import</span> sessionmaker</span><br><span class="line"></span><br><span class="line">engine = create_engine(<span class="string">&#x27;sqlite:///test.sqlite3&#x27;</span>)</span><br><span class="line">Session = sessionmaker(bind=engine)</span><br><span class="line"></span><br><span class="line">session = Session()</span><br></pre></td></tr></table></figure>

<h3 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 插入单条</span></span><br><span class="line">session.add(SomeData(status=<span class="string">&#x27;1&#x27;</span>, message=<span class="string">&#x27;aa&#x27;</span>))</span><br><span class="line"><span class="comment"># 插入多条</span></span><br><span class="line">session.add_all([</span><br><span class="line">    SomeData(status=<span class="string">&#x27;2&#x27;</span>, message=<span class="string">&#x27;bb&#x27;</span>),</span><br><span class="line">    SomeData(status=<span class="string">&#x27;2&#x27;</span>, message=<span class="string">&#x27;cc&#x27;</span>),</span><br><span class="line">])</span><br><span class="line">session.commit()</span><br><span class="line"><span class="comment"># session.rollback()  # 也可以回滚</span></span><br></pre></td></tr></table></figure>

<h3 id="查询"><a href="#查询" class="headerlink" title="查询"></a>查询</h3><p>一些基本查询，更多查看<a href="https://docs.sqlalchemy.org/en/13/orm/tutorial.html#common-filter-operators">官方文档</a></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">## select * from table11</span></span><br><span class="line">session.query(SomeData).<span class="built_in">all</span>()</span><br><span class="line"><span class="comment"># return: [&lt;__main__.SomeData object at 0x1040e6278&gt;, &lt;__main__.SomeData object at 0x103aa13c8&gt;, &lt;__main__.SomeData object at 0x103aa1438&gt;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 通过主键获取数据</span></span><br><span class="line">session.query(SomeData).get(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">## select * from table11 where status=&#x27;2&#x27; order by the_time limit 1</span></span><br><span class="line">data = session.query(SomeData).filter_by(status=<span class="string">&#x27;2&#x27;</span>).order_by(SomeData.the_time).first()</span><br><span class="line"><span class="comment"># data: &lt;__main__.SomeData object at 0x103aa13c8&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## select * from table11 where status in (&#x27;1&#x27;, &#x27;2&#x27;)</span></span><br><span class="line">session.query(SomeData).<span class="built_in">filter</span>(SomeData.status.in_([<span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;2&#x27;</span>])).<span class="built_in">all</span>()</span><br><span class="line"></span><br><span class="line"><span class="comment">## select status, message from table11</span></span><br><span class="line">session.query(SomeData.status, SomeData.message).<span class="built_in">all</span>()</span><br><span class="line"><span class="comment"># return: [(&#x27;1&#x27;, &#x27;aa&#x27;), (&#x27;2&#x27;, &#x27;bb&#x27;), (&#x27;2&#x27;, &#x27;cc&#x27;)]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 原始sql语句</span></span><br><span class="line">stmt = text(<span class="string">&quot;SELECT message, id, status FROM table11 Where status=:status&quot;</span>)</span><br><span class="line">stmt = stmt.columns(SomeData.message, SomeData.<span class="built_in">id</span>, SomeData.status,)</span><br><span class="line">session.query(SomeData).from_statement(stmt).params(status=<span class="string">&#x27;1&#x27;</span>).<span class="built_in">all</span>()</span><br><span class="line"></span><br><span class="line"><span class="comment">## 遍历大表，每N条数据请求数据库一次</span></span><br><span class="line"><span class="keyword">for</span> p <span class="keyword">in</span> session.query(SomeData).yield_per(<span class="number">5</span>):</span><br><span class="line">    <span class="built_in">print</span>(p)</span><br></pre></td></tr></table></figure>

<p>多表join查询</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">for</span> u, a <span class="keyword">in</span> session.query(User, Address).\</span><br><span class="line"><span class="meta">... </span>                    <span class="built_in">filter</span>(User.<span class="built_in">id</span>==Address.user_id).\</span><br><span class="line"><span class="meta">... </span>                    <span class="built_in">filter</span>(Address.email_address==<span class="string">&#x27;jack@google.com&#x27;</span>).\</span><br><span class="line"><span class="meta">... </span>                    <span class="built_in">all</span>():</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(u)</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span>(a)</span><br><span class="line">&lt;User(name=<span class="string">&#x27;jack&#x27;</span>, fullname=<span class="string">&#x27;Jack Bean&#x27;</span>, nickname=<span class="string">&#x27;gjffdd&#x27;</span>)&gt;</span><br><span class="line">&lt;Address(email_address=<span class="string">&#x27;jack@google.com&#x27;</span>)&gt;</span><br></pre></td></tr></table></figure>

<h3 id="更新"><a href="#更新" class="headerlink" title="更新"></a>更新</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment">## 更新单条</span></span><br><span class="line">data = session.query(SomeData).get(<span class="number">1</span>)</span><br><span class="line">data.cost_time = <span class="number">22</span></span><br><span class="line">session.commit()</span><br><span class="line"></span><br><span class="line"><span class="comment">## 批量更新</span></span><br><span class="line">session.query(SomeData).update(&#123;SomeData.cost_time: <span class="string">&#x27;3456&#x27;</span>&#125;)</span><br><span class="line">session.commit()</span><br></pre></td></tr></table></figure>

<h3 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">session.query(SomeData).filter_by(<span class="built_in">id</span>=<span class="number">2</span>).delete()</span><br><span class="line">session.commit()</span><br></pre></td></tr></table></figure>

<h2 id="seesion管理最佳实践"><a href="#seesion管理最佳实践" class="headerlink" title="seesion管理最佳实践"></a>seesion管理最佳实践</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> contextlib <span class="keyword">import</span> contextmanager</span><br><span class="line"><span class="keyword">from</span> sqlalchemy <span class="keyword">import</span> create_engine</span><br><span class="line"><span class="keyword">from</span> sqlalchemy.orm <span class="keyword">import</span> sessionmaker</span><br><span class="line"><span class="keyword">from</span> sqlalchemy.orm <span class="keyword">import</span> scoped_session  <span class="comment"># 使session可以用于多线程环境</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># pool_recycle 数据库连接的回收周期, 按需调整</span></span><br><span class="line"><span class="comment"># pool_size 连接池大小, 按需调整, 0为不限制连接数</span></span><br><span class="line"><span class="comment"># pool_pre_ping=True 每次从连接池中取出连接时, 都判断是否有效, 可替代pool_recycle参数</span></span><br><span class="line"><span class="comment"># engine = create_engine(&#x27;mysql://database&#x27;, pool_pre_ping=True, pool_size=0)</span></span><br><span class="line">engine = create_engine(<span class="string">&#x27;sqlite:///test.sqlite3&#x27;</span>)</span><br><span class="line"><span class="comment"># scoped_session 可在多线程环境使用，但不支持session嵌套</span></span><br><span class="line"><span class="comment"># Session = scoped_session(sessionmaker(bind=engine))</span></span><br><span class="line">Session = sessionmaker(bind=engine)</span><br><span class="line"></span><br><span class="line"><span class="comment"># session 自动管理</span></span><br><span class="line"><span class="meta">@contextmanager</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_session</span>():</span><br><span class="line">    session = Session()</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">yield</span> session</span><br><span class="line">        session.commit()</span><br><span class="line">    <span class="keyword">except</span>:</span><br><span class="line">        session.rollback()</span><br><span class="line">        <span class="keyword">raise</span></span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        session.close()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用示例</span></span><br><span class="line"><span class="keyword">with</span> get_session() <span class="keyword">as</span> session:</span><br><span class="line">    session.execute(<span class="string">&#x27;select 1&#x27;</span>)</span><br><span class="line">    session.add(SomeData(status=<span class="string">&#x27;1&#x27;</span>, message=<span class="string">&#x27;aa&#x27;</span>))</span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>SQLAlchemy</tag>
        <tag>ORM</tag>
      </tags>
  </entry>
  <entry>
    <title>Windows Subsystem for Linux(WSL) 简单指引</title>
    <url>/2017/01/08/the-guide-of-windows-subsystem-for-linux/</url>
    <content><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>　　<code>Windows Subsystem for Linux</code>（简称WSL）是一个为在Windows 10上能够原生运行Linux二进制可执行文件（ELF格式）的兼容层。它是由微软与Canonical公司合作开发，目标是使纯正的Ubuntu 14.04 “Trusty Tahr”映像能下载和解压到用户的本地计算机，并且映像内的工具和实用工具能在此子系统上原生运行。<br>　　WSL提供了一个微软开发的Linux兼容内核接口（不包含Linux代码），来自Ubuntu的用户模式二进制文件在其上运行。</p>
<p>　　此功能组件从Win10 Insider Preview build 14316开始可用，正式版是Win10 RedStone1才可用，并且只有64位系统才有此功能。我目前的系统是预览版Insider Preview build 14986，相比正式版Win10 RedStone1版本WSL功能会完善些，但系统就不稳定些了。建议大家还是用Win10 RedStone1吧</p>
<p>　　WSL 的出现解决了很大程度上解决了Windows用户使用linux工具链的需求，同时也解决部分用户（比如我）在Linux与Windows之间切换的麻烦。</p>
<p><strong>WSL的优点</strong></p>
<ul>
<li>提高了接近原生Linux的使用体验，Python，Shell等开发环境与linux下基本相同</li>
<li>使用Bash进行一些日常操作比CMD高校和方便多了</li>
<li>资源利用效率，以及启动速度比虚拟机快多了，而且不用担心文件共享的问题</li>
</ul>
<span id="more"></span>

<p><strong>WSL的不足</strong></p>
<ul>
<li>WSL目前属于测试阶段（beta），一些与内核关系紧密的软件包就不能用了，比如<code>systemd</code>，<code>iptables</code>等</li>
<li>关闭WSL窗口后，后台进程全部会退出，实现后台服务有难度。</li>
<li>WSL内部和外部的二进制程序并不能通用，也就是WSL里不能调用Win程序，Win也不能调用WSL里的程序。</li>
<li>文件权限管理，在WSL中可访问win中的文件，不过文件的权限均为777</li>
</ul>
<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p><strong>由于本文写作时间已经比较久了，强烈建议查看MS的官方教程</strong><a href="https://msdn.microsoft.com/en-us/commandline/wsl/install_guide">WSL install guide</a></p>
<ol>
<li><p>开启开发者模式： 设置 &gt; 更新及安全 &gt; 针对开发人员 &gt; 开发人员模式<br><img data-src="https://image.ponder.work/mweb/17-1-8/91407501-file_1483888101129_101b0.gif"></p>
</li>
<li><p>启用WSL功能：资源管理器地址栏输入 “控制面板\程序\程序和功能”，选择启用或关闭Windows功能，勾选适用于Linux的Windwos子系统(beta)，重启系统<br><img data-src="https://image.ponder.work/mweb/17-1-8/87068378-file_1483888094877_143b7.png"></p>
</li>
<li><p>下载Linux镜像：按<code>Win + X</code>选择“命令提示符”或者“Windows PowerShell”，在命令行中输入<code>bash</code>，按提示操作。安装完成后使用也是在命令行中输入<code>bash</code><br><img data-src="https://image.ponder.work/mweb/17-1-8/71864116-file_1483888221167_146b1.png"><br><img data-src="https://image.ponder.work/mweb/17-1-8/275971-file_1483888221026_4896.png"></p>
</li>
</ol>
<h2 id="替换WSL终端"><a href="#替换WSL终端" class="headerlink" title="替换WSL终端"></a>替换WSL终端</h2><p>可以用<code>mintty</code>作为WSL的终端，替换命令提示符，获得更类似linux的体验。github中已经有人做好了，下载使用即可<a href="https://github.com/goreliu/wsl-terminal/releases">goreliu&#x2F;wsl-terminal</a><br>解压运行open-wsl.exe，你就得到了一个漂亮的linux终端。<br><img data-src="https://image.ponder.work/mweb/17-1-8/57114153-file_1483889057173_a2dd.png"></p>
<h2 id="在右键菜单中添加在此处打开WSL"><a href="#在右键菜单中添加在此处打开WSL" class="headerlink" title="在右键菜单中添加在此处打开WSL"></a>在右键菜单中添加在此处打开WSL</h2><p>新建文本文件，用记事本打开，输入一下内容，替换里面的两处路径，保存重命名为<code>wsl.reg</code>，运行。</p>
<figure class="highlight lsl"><table><tr><td class="code"><pre><span class="line">Windows Registry Editor Version <span class="number">5.00</span></span><br><span class="line"></span><br><span class="line">[HKEY_CLASSES_ROOT\Directory\Background\shell\wsl]</span><br><span class="line">@=<span class="string">&quot;WSL Here&quot;</span></span><br><span class="line"><span class="string">&quot;Icon&quot;</span>=<span class="string">&quot;<span class="subst">\&quot;</span>D:<span class="subst">\\</span>PortableApps<span class="subst">\\</span>wsl-terminal<span class="subst">\\</span>open-wsl.ico<span class="subst">\&quot;</span>&quot;</span></span><br><span class="line"></span><br><span class="line">[HKEY_CLASSES_ROOT\Directory\Background\shell\wsl\command]</span><br><span class="line">@=<span class="string">&quot;D:<span class="subst">\\</span>PortableApps<span class="subst">\\</span>wsl-terminal<span class="subst">\\</span>open-wsl.exe&quot;</span></span><br></pre></td></tr></table></figure>
<p><img data-src="https://image.ponder.work/mweb/17-1-8/4400425-file_1483888101002_ba17.png"></p>
<h2 id="运行linux桌面"><a href="#运行linux桌面" class="headerlink" title="运行linux桌面"></a>运行linux桌面</h2><ol>
<li><p>在WSL里安装好桌面，<code>apt-get -y install xorg xfce4</code></p>
</li>
<li><p>在win上安装<code>x server</code>，可选<code>xming</code>和<code>VcXsrv</code>，这里选用<a href="https://sourceforge.net/projects/vcxsrv/">VcXsrv</a></p>
</li>
<li><p>启动<code>x server</code><br><img data-src="https://image.ponder.work/mweb/17-1-8/7879750-file_1483888094999_453c.png"><br><img data-src="https://image.ponder.work/mweb/17-1-8/86091829-file_1483889277522_60b1.png"><br><img data-src="https://image.ponder.work/mweb/17-1-8/74401307-file_1483888095234_162aa.png"><br><img data-src="https://image.ponder.work/mweb/17-1-8/59701947-file_1483888095343_17660.png"><br><img data-src="https://image.ponder.work/mweb/17-1-8/85096906-file_1483888095449_1787e.png"></p>
</li>
<li><p>WSL的配置，在wsl的bash中运行</p>
</li>
</ol>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;export DISPLAY=:0.0&quot;</span> &gt;&gt; ~/.bashrc  <span class="comment"># 设置屏幕为x server</span></span><br><span class="line"><span class="built_in">source</span> ~/.bashrc</span><br><span class="line"><span class="built_in">sudo</span> sed -i <span class="string">&#x27;s$&lt;listen&gt;.*&lt;/listen&gt;$&lt;listen&gt;tcp:host=localhost,port=0&lt;/listen&gt;$&#x27;</span> /etc/dbus-1/session.conf  <span class="comment"># 解决D-bus的问题</span></span><br></pre></td></tr></table></figure>
<ol start="5">
<li>在WSL里运行<code>startxfce4</code>，就此大功告成<br><img data-src="https://image.ponder.work/mweb/17-1-8/26700478-file_1483888100890_f880.png"><br><img data-src="https://image.ponder.work/mweb/17-1-8/4581644-file_1483888100682_e456.png"></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Linux</tag>
        <tag>WSL</tag>
      </tags>
  </entry>
  <entry>
    <title>语言的力量</title>
    <url>/2025/05/27/the-power-of-language/</url>
    <content><![CDATA[<p>语言的力量远比想象的强大，某种程度上是有虚空造物的能力。</p>
<span id="more"></span>
<p>你可以凭空创造一个概念，并植入你的听众脑子，不论好还是坏。诈骗分子、PUA 大师、营销大师无不精通此等技艺。</p>
<p>起因是一个综艺节目，有个导演对哈妮克孜说了句话，让我惊为天人，让我惊叹“你他娘真是个人才”。<br>它在线下拍戏的时候见到了哈妮克孜，哈当时戴了副大镜框眼镜，于是它说道“我当时看到你的时候，感觉好失望，你和我银幕上看到的哈妮克孜完全不一样”。<br>它凭空创造了一个观念（事实），哈妮克孜颜值不行。怎么创造的呢？和哈妮克孜荧幕形象相比。这显然是不客观的，但很难瞬间就察觉到。</p>
<p>这样，好与坏、美与丑一切观念都是可以被重新定义的，只要你选择合适的目标和对照标准，甚至是你脑海里的标准。<br>你是丑的和你漂亮的时候比，你是蠢的和聪明的时候比，我对你是失望的和对你的期望相比。<br>PUA 大师就是用这种手法实现打压，新闻学就是这么颠倒黑白，当然也可以反向操作，那么你就成为知心姐姐鼓励大师。</p>
<p>所以，神话中的言出法随也不难理解，毕竟人是观念的动物，你改变&#x2F;植入了某人的某个观念，那么你就改变了他眼中的世界。<br>这也就是《乔布斯传》中乔布斯的扭曲现实力场。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
      </tags>
  </entry>
  <entry>
    <title>ubuntu静态ip配置</title>
    <url>/2016/04/16/ubuntu%E9%9D%99%E6%80%81ip%E9%85%8D%E7%BD%AE/</url>
    <content><![CDATA[<p>以vmware虚拟机为例</p>
<h2 id="设置IP"><a href="#设置IP" class="headerlink" title="设置IP"></a>设置IP</h2><p>运行 <code>sudo nano /etc/network/interfaces</code><br>将文件修改成如下：</p>
<figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">auto</span> eth0  <span class="comment"># eth0是网卡名称，你的不一定是这个，可通过ifconfig查看</span></span><br><span class="line">iface eth0 inet static</span><br><span class="line">address <span class="number">192.168.157.129</span>   <span class="comment"># 地址</span></span><br><span class="line">gateway <span class="number">192.168.157.2</span>    <span class="comment"># 网关</span></span><br><span class="line">netmask <span class="number">255.255.255.0</span>   <span class="comment"># 掩码</span></span><br></pre></td></tr></table></figure>

<h2 id="修改DNS"><a href="#修改DNS" class="headerlink" title="修改DNS"></a>修改DNS</h2><p>运行 <code>sudo nano /etc/resolvconf/resolv.conf.d/base</code><br>把文件改成 <code>nameserver 192.168.157.2</code><br>把<code>192.168.157.2</code>改成你需要的dns，这里因为是vmware的NAT模式，所以dns和网关是一样的。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Linux</tag>
        <tag>Ubuntu</tag>
      </tags>
  </entry>
  <entry>
    <title>variable-precision SWAR 算法详解</title>
    <url>/2020/08/01/variable-precision-SWAR-algorithm/</url>
    <content><![CDATA[<p>在学习redis源码时，发现<code>BITCOUNT</code>命令实现用到了variable-precision SWAR 算法。</p>
<p><code>BITCOUNT</code>命令要解决的问题：<strong>统计一个位数组中非0二进制位的数量</strong>。在数学上被称为“计算汉明重量（Hamming Weight)”</p>
<p>目前已知效率最好的通用算法为variable-precision SWAR 算法。<br>该算法通过一系列位移和位运算操作，可以在常数时间内计算多个字节的汉明重量，并且不需要使用任何额外的内存。</p>
<span id="more"></span>
<h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>以下是一个处理32位长度位数组的算法实现，一共分4步。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">uint32_t</span> <span class="title function_">swar</span><span class="params">(<span class="type">uint32_t</span> i)</span>&#123;</span><br><span class="line">    i = (i &amp; <span class="number">0x55555555</span>) + ((i&gt;&gt;<span class="number">1</span>) &amp; <span class="number">0x55555555</span>);  <span class="comment">// 步骤1</span></span><br><span class="line">    i = (i &amp; <span class="number">0x33333333</span>) + ((i&gt;&gt;<span class="number">2</span>) &amp; <span class="number">0x33333333</span>);  <span class="comment">// 步骤2</span></span><br><span class="line">    i = (i &amp; <span class="number">0x0F0F0F0F</span>) + ((i&gt;&gt;<span class="number">4</span>) &amp; <span class="number">0x0F0F0F0F</span>);  <span class="comment">// 步骤3</span></span><br><span class="line">    i = (i * <span class="number">0x01010101</span>) &gt;&gt; <span class="number">24</span>;                    <span class="comment">// 步骤4</span></span><br><span class="line">    <span class="keyword">return</span> i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h2><p>这里我们以<code>i=0x12345678</code>(二进制位为<code>00010010001101000101011001111000</code>)为例，讲解算法过程<br>我们可以把i的二进制位理解成：长度为32的数组，每个元素取值区间[0,1]，每个元素正好能代表这个位是不是1.</p>
<p>所以，问题就可以转化为，求这个数组的和。<br>根据分治法的思想，我们可以把相邻的两个数字相加，得到长度为16的数组，每个元素取值区间[0,2]。<br>并以此类推，最终求出总和。</p>
<p><img data-src="https://image.ponder.work/mweb/2020-08-01-15962704953270.jpg"><br><img data-src="https://image.ponder.work/mweb/2020-08-01-15962705553725.jpg"></p>
<h3 id="步骤1"><a href="#步骤1" class="headerlink" title="步骤1"></a>步骤1</h3><p>这一步用到<code>0x55555555</code>作为掩码，其二进制表示为<code>01010101010101010101010101010101</code><br>此时i可理解为长度为32的数组，每个元素取值区间[0,1]，元素宽度1bit。</p>
<p>通过<code>i &amp; 0x55555555</code>运算，取得了i的奇数位置元素，存储为16个2bit整数；<br>通过<code>(i&gt;&gt;1) &amp; 0x55555555</code>运算，取得了i的偶数位置元素，存储为16个2bit整数；</p>
<p>两者相加，相当于16组2bit整数按位相加，问题就转化成了2bit的二进制加法。<br>由于原数组每个元素取值区间[0,1]，所以每组相加的结果会在[0,2]区间内，2bit刚好存储。<br>最终得到长度为16的数组，每个元素取值区间[0,2]。<br><img data-src="https://image.ponder.work/mweb/2020-08-01-15962660065367.jpg"></p>
<h3 id="步骤2"><a href="#步骤2" class="headerlink" title="步骤2"></a>步骤2</h3><p>这一步用到<code>0x33333333</code>作为掩码，其二进制表示为<code>00110011001100110011001100110011</code><br>此时i可理解表示为长度为16的数组，每个元素取值区间[0,2]，元素宽度2bit。</p>
<p>通过<code>i &amp; 0x33333333</code>运算，取得了i的奇数位置元素，存储为8个4bit整数；<br>通过<code>(i&gt;&gt;1) &amp; 0x33333333</code>运算，取得了i的偶数位置元素，存储为8个4bit整数；</p>
<p>两者相加，相当于8组4bit整数按位相加，问题就转化成了4bit的二进制加法。<br>由于原数组每个元素取值区间[0,2]，所以每组相加的结果会在[0,4]区间内，4bit刚好存储。<br>最终得到长度为8的数组，每个元素取值区间[0,4]。</p>
<h3 id="步骤3"><a href="#步骤3" class="headerlink" title="步骤3"></a>步骤3</h3><p>这一步用到<code>0x0F0F0F0F</code>作为掩码，其二进制表示为<code>00001111000011110000111100001111</code><br>此时i可理解表示为长度为8的数组，每个元素取值区间[0,4]，元素宽度4bit。</p>
<p>通过<code>i &amp; 0x0F0F0F0F</code>运算，取得了i的奇数位置元素，存储为4个8bit整数；<br>通过<code>(i&gt;&gt;1) &amp; 0x33333333</code>运算，取得了i的偶数位置元素，存储为4个8bit整数；</p>
<p>两者相加，相当于4组8bit整数按位相加, 问题就转化成了8bit的二进制加法。<br>由于原数组每个元素取值区间[0,4]，所以每组相加的结果会在<code>[0,8]</code>区间内，8bit足够存储。<br>最终得到长度为4的数组，每个元素取值区间[0,8]。</p>
<h3 id="步骤4"><a href="#步骤4" class="headerlink" title="步骤4"></a>步骤4</h3><p>按照上面的思路，本来应该继续将长度为4的数组转换为长度为2的数组。<br>但是这里由于4个8bit整数相加存在简便运算，就不继续往下合并了。</p>
<p>到这一步是时<code>i=0x02030404</code>，为了求出最终结果，我们可以想到位移的办法将每8bit取出（参考ip掩码计算），然后再依次相加。<br>最终结果也就是 <code>(i &amp; 0xFF) + ((i&gt;&gt;8) &amp; 0xFF) + ((i&gt;&gt;16) &amp; 0xFF) + ((i&gt;&gt;24) &amp; 0xFF)</code></p>
<p>为了理解算法里的做法，这里需要简单的数学推导</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 将0x01010101转化成多项式表达</span></span><br><span class="line">          <span class="number">0x01010101</span> == <span class="number">2</span>**<span class="number">24</span> + <span class="number">2</span>**<span class="number">16</span> + <span class="number">2</span>**<span class="number">8</span> + <span class="number">2</span>**<span class="number">0</span></span><br><span class="line"><span class="comment">// 两边同乘以i</span></span><br><span class="line">      i * <span class="number">0x01010101</span> == i * <span class="number">2</span>**<span class="number">24</span> + i * <span class="number">2</span>**<span class="number">16</span> + i * <span class="number">2</span>**<span class="number">8</span> + i * <span class="number">2</span>**<span class="number">0</span></span><br><span class="line"><span class="comment">// 2的乘方运算转化为位移运算</span></span><br><span class="line">      i * <span class="number">0x01010101</span> == (i&lt;&lt;<span class="number">24</span>) + (i&lt;&lt;<span class="number">16</span>) + (i&lt;&lt;<span class="number">8</span>) + (i&lt;&lt;<span class="number">0</span>)</span><br><span class="line"><span class="comment">// 两边同时右移24位</span></span><br><span class="line">(i * <span class="number">0x01010101</span>)&gt;&gt;<span class="number">24</span> == ((i&lt;&lt;<span class="number">24</span>)&gt;&gt;<span class="number">24</span>) + ((i&lt;&lt;<span class="number">16</span>)&gt;&gt;<span class="number">24</span>) + ((i&lt;&lt;<span class="number">8</span>)&gt;&gt;<span class="number">24</span>) + ((i&lt;&lt;<span class="number">0</span>)&gt;&gt;<span class="number">24</span>)</span><br><span class="line"><span class="comment">// 将左移和右移合并，并考虑溢出，最终结果一致</span></span><br><span class="line">(i * <span class="number">0x01010101</span>)&gt;&gt;<span class="number">24</span> == (i &amp; <span class="number">0xFF</span>) + ((i&gt;&gt;<span class="number">8</span>) &amp; <span class="number">0xFF</span>) + ((i&gt;&gt;<span class="number">16</span>) &amp; <span class="number">0xFF</span>) + ((i&gt;&gt;<span class="number">24</span>) &amp; <span class="number">0xFF</span>)</span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>算法</tag>
      </tags>
  </entry>
  <entry>
    <title>在 VSCode 中用 Rust 刷LeetCode</title>
    <url>/2025/03/08/vscode-leetcode-rust/</url>
    <content><![CDATA[<p>本文介绍在 VSCode 中配置和使用插件来高效地解决 LeetCode 问题，并使用 Rust 语言编写和测试代码。</p>
<span id="more"></span>

<h2 id="vscode-插件"><a href="#vscode-插件" class="headerlink" title="vscode 插件"></a>vscode 插件</h2><ul>
<li>LeetCode.vscode-leetcode</li>
<li>pucelle.run-on-save</li>
<li>rust-lang.rust-analyzer</li>
</ul>
<h2 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h2><p>cargo new vscode-leetcode-rust</p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line"><span class="comment">// tree -I target --dirsfirst</span></span><br><span class="line">.</span><br><span class="line">├── Cargo<span class="selector-class">.lock</span></span><br><span class="line">├── Cargo<span class="selector-class">.toml</span></span><br><span class="line">└── <span class="attribute">src</span></span><br><span class="line">    ├── lib<span class="selector-class">.rs</span></span><br><span class="line">    ├── <span class="selector-tag">main</span><span class="selector-class">.rs</span></span><br><span class="line">    └── solutions</span><br><span class="line">        ├── <span class="number">1</span>_two_sum<span class="selector-class">.rs</span></span><br><span class="line">        ...</span><br></pre></td></tr></table></figure>

<h2 id="vscode-全局设置"><a href="#vscode-全局设置" class="headerlink" title="vscode 全局设置"></a>vscode 全局设置</h2><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="attr">&quot;leetcode.useEndpointTranslation&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span>  <span class="comment">// for english filename</span></span><br><span class="line"><span class="attr">&quot;leetcode.workspaceFolder&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/Users/&lt;your_name&gt;/projects/vscode-leetcode-cn-rust&quot;</span><span class="punctuation">,</span>   </span><br><span class="line"><span class="attr">&quot;leetcode.filePath&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;default&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;folder&quot;</span><span class="punctuation">:</span> <span class="string">&quot;src/solutions&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;filename&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;id&#125;_$&#123;snake_case_name&#125;.$&#123;ext&#125;&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure>

<h2 id="用-automod-宏添加新回答到模块"><a href="#用-automod-宏添加新回答到模块" class="headerlink" title="用 automod 宏添加新回答到模块"></a>用 automod 宏添加新回答到模块</h2><p>cargo add automod</p>
<figure class="highlight rust"><table><tr><td class="code"><pre><span class="line"><span class="comment">// src/lib.rs </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> CURRENT: &amp;<span class="type">str</span> = <span class="string">&quot;sdfsdfsd.rs&quot;</span>;  <span class="comment">// trigger rust-analyser recheck</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">mod</span> solutions &#123;</span><br><span class="line">    automod::dir!(<span class="string">&quot;src/solutions&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="触发-rust-analyzer"><a href="#触发-rust-analyzer" class="headerlink" title="触发 rust-analyzer"></a>触发 rust-analyzer</h2><p>用 run-on-save 插件，保存回答时更新 lib.rs，触发 rust-analyzer 重新分析项目，开启新回答的代码补全。</p>
<p>vscode 项目配置</p>
<figure class="highlight dart"><table><tr><td class="code"><pre><span class="line"><span class="string">&quot;runOnSave.commands&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">         <span class="comment">// use gnu sed update lib.rs when add solutions (macOS)</span></span><br><span class="line">        <span class="string">&quot;command&quot;</span>: <span class="string">&quot;sh onsave.sh <span class="subst">$&#123;fileBasename&#125;</span>&quot;</span>,  </span><br><span class="line">        <span class="string">&quot;runIn&quot;</span>: <span class="string">&quot;backend&quot;</span>,</span><br><span class="line">        <span class="string">&quot;finishStatusMessage&quot;</span>: <span class="string">&quot;touched <span class="subst">$&#123;workspaceFolderBasename&#125;</span>&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure>


<p><code>onsave.sh</code>脚本，macOS使用 gnused，linux 使用默认 sed 就好。<br>通过切换模块是否为pub来触发 rust-analyzer 识别新回答</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">FILE=<span class="variable">$1</span></span><br><span class="line"><span class="keyword">if</span> [[ `grep <span class="string">&quot;<span class="variable">$FILE</span>&quot;</span> src/lib.rs | <span class="built_in">wc</span> -l` -eq 0 ]]; <span class="keyword">then</span></span><br><span class="line">    gsed -i -E <span class="string">&quot;/CURRENT/c\const CURRENT: &amp;str = \&quot;<span class="variable">$FILE</span>\&quot;;&quot;</span> src/lib.rs</span><br><span class="line">    <span class="keyword">if</span> [[ `grep <span class="string">&#x27;::dir!(pub&#x27;</span> src/lib.rs | <span class="built_in">wc</span> -l` -eq 1 ]]; <span class="keyword">then</span></span><br><span class="line">        gsed -i <span class="string">&quot;s|::dir!(pub |::dir!(|&quot;</span> src/lib.rs</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        gsed -i <span class="string">&quot;s|::dir!(|::dir!(pub |&quot;</span> src/lib.rs</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h2 id="编写本地测试用例"><a href="#编写本地测试用例" class="headerlink" title="编写本地测试用例"></a>编写本地测试用例</h2><p>把测试代码写在 <code>&quot;// @lc code=end&quot;</code> 后面，需要定义 Solution 结构体，可能还需要定义参数的结构体。</p>
<figure class="highlight elixir"><table><tr><td class="code"><pre><span class="line">// <span class="variable">@lc</span> code=<span class="keyword">end</span></span><br><span class="line">struct <span class="title class_">Solution</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">#[test]</span></span><br><span class="line"><span class="keyword">fn</span> test_a() &#123;</span><br><span class="line">    // let res = <span class="title class_">Solution</span>::is_valid(<span class="title class_">String</span>::from(<span class="string">&quot;()[]&#123;&#125;&quot;</span>));</span><br><span class="line">    // println!(<span class="string">&quot;RESUTL\t&#123;:?&#125;&quot;</span>, res);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img data-src="https://image.ponder.work/mweb/2025-03-16---17420958075794.jpg" alt="-w340"></p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Rust</tag>
      </tags>
  </entry>
  <entry>
    <title>Windows 网络共享</title>
    <url>/2021/10/10/windows-network-share/</url>
    <content><![CDATA[<p>要在不暴露 client 的情况下共享网络，一般就只能使用 nat(Network Address Translation), linux 下可以使用 iptables 很轻松地搞定。<br>nat 包含 DNAT 和 SNAT, 要想双向互通，必须两者都实现。</p>
<p>windows下的网络共享只有SNAT那一部分，比如各自免费wifi软件。少了DNAT，外部网络就无法访问内部。</p>
<p>还好windows下可以配置端口转发，实现等效的DNAT</p>
<span id="more"></span>
<h2 id="先配置网络共享"><a href="#先配置网络共享" class="headerlink" title="先配置网络共享"></a>先配置网络共享</h2><p>前置要求，需要两张网卡，无线或者有线均可。</p>
<ul>
<li>A: 用于访问外网</li>
<li>B: 共享网络接入点</li>
</ul>
<p>打开：控制面板 》网络和Internet 》网络和共享中心 》更改适配器设置</p>
<p><img data-src="https://image.ponder.work/mweb/2021-10-10-16338760513347.jpg"></p>
<p><img data-src="https://image.ponder.work/mweb/2021-10-10-16338761455929.jpg"></p>
<p>右键A网卡 &gt; 属性 &gt; 共享 &gt; 勾选允许 （win10可能有下拉选择，下拉选中B网卡）<br><img data-src="https://image.ponder.work/mweb/2021-10-10-16338761903207.jpg"></p>
<h2 id="端口映射配置"><a href="#端口映射配置" class="headerlink" title="端口映射配置"></a>端口映射配置</h2><p>netsh是Windows自带的端口转发&#x2F;端口映射工具。</p>
<p>支持IPv4和IPv6,命令即时生效,重启系统后配置仍然存在。</p>
<h3 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h3><ul>
<li>add - 在一个表格中添加一个配置项。</li>
<li>delete - 从一个表格中删除一个配置项。</li>
<li>dump - 显示一个配置脚本。</li>
<li>help - 显示命令列表。</li>
<li>reset - 重置端口代理配置状态。</li>
<li>set - 设置配置信息。</li>
<li>show - 显示信息。</li>
</ul>
<h3 id="用法-以v4tov4为例"><a href="#用法-以v4tov4为例" class="headerlink" title="用法(以v4tov4为例)"></a>用法(以v4tov4为例)</h3><figure class="highlight inform7"><table><tr><td class="code"><pre><span class="line">add v4tov4 <span class="comment">[listenport=]</span>integer&gt;|servicename&gt; \</span><br><span class="line">           <span class="comment">[connectaddress=]</span>IPv4 address&gt;|hostname&gt;  \</span><br><span class="line">           <span class="comment">[<span class="comment">[connectport=]</span>integer&gt;|servicename&gt;]</span>  \</span><br><span class="line">           <span class="comment">[<span class="comment">[listenaddress=]</span>IPv4 address&gt;|hostname&gt;]</span>  \</span><br><span class="line">           <span class="comment">[<span class="comment">[protocol=]</span>tcp]</span></span><br></pre></td></tr></table></figure>

<h3 id="参数说明"><a href="#参数说明" class="headerlink" title="参数说明"></a>参数说明</h3><ul>
<li>listenport      - IPv4 侦听端口。</li>
<li>connectaddress  - IPv4 连接地址。</li>
<li>connectport     - IPv4 连接端口。</li>
<li>listenaddress   - IPv4 侦听地址。</li>
<li>protocol     - 使用的协议。现在只支持 TCP</li>
</ul>
<h3 id="案例-ssh端口转发"><a href="#案例-ssh端口转发" class="headerlink" title="案例(ssh端口转发)"></a>案例(ssh端口转发)</h3><p>将192.168.8.108的22端口映射到本地的2222端口</p>
<p>这样外部就可以通过本地的对外ip来ssh访问192.168.8.108了</p>
<figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">netsh<span class="built_in"> interface </span>portproxy <span class="built_in">add</span> v4tov4 <span class="attribute">listenport</span>=2222 <span class="attribute">connectaddress</span>=192.168.8.108 <span class="attribute">connectport</span>=22</span><br></pre></td></tr></table></figure>

<h3 id="显示端口转发"><a href="#显示端口转发" class="headerlink" title="显示端口转发"></a>显示端口转发</h3><p>一般情况下使用下列命令进行查看</p>
<figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">netsh <span class="keyword">interface</span> <span class="symbol">portproxy</span> <span class="symbol">show</span> <span class="symbol">all</span></span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>Windows</tag>
      </tags>
  </entry>
  <entry>
    <title>windows优秀软件推荐（多图注意流量）</title>
    <url>/2016/07/31/windows%E8%BD%AF%E4%BB%B6%E6%8E%A8%E8%8D%90/</url>
    <content><![CDATA[<p>本人结合自己使用的感受，推荐以下几十款软件给大家使用，覆盖了日常的大部分使用场景。<br>软件不追新，够用就好，因为软件随着不断更新过度开发的可能性是很大的。<br>资源大部分来自互联网，请知悉相关风险。</p>
<p>部分分享已经失效，这里补一下: <a href="https://pan.baidu.com/s/1ehg5VNksD7u-eWm1KFmPSw">https://pan.baidu.com/s/1ehg5VNksD7u-eWm1KFmPSw</a><br><em>提取码: g2fx</em></p>
<p><strong>善用佳软，节约时间，提高效率。</strong></p>
<span id="more"></span>

<h2 id="网页浏览"><a href="#网页浏览" class="headerlink" title="网页浏览"></a>网页浏览</h2><h3 id="Cent-Browser"><a href="#Cent-Browser" class="headerlink" title="Cent Browser"></a>Cent Browser</h3><p>一款基于谷歌浏览器优化的浏览器，本人日用。<br>对谷歌浏览器添加以下优化</p>
<ul>
<li>双击关闭标签页</li>
<li>快速拖拽</li>
<li>鼠标手势</li>
<li>提供免安装绿色版</li>
<li>可选隐藏头像按钮</li>
<li>小窗口播放视频</li>
<li>快速存图</li>
<li>等等。。。</li>
</ul>
<div style="max-width: 600px">![](https://image.ponder.work/mweb/16-7-31/99975896.jpg)</div>
**下载地址** [官方网站](http://www.centbrowser.com/)

<h2 id="媒体播放"><a href="#媒体播放" class="headerlink" title="媒体播放"></a>媒体播放</h2><h3 id="AirPlay"><a href="#AirPlay" class="headerlink" title="AirPlay"></a>AirPlay</h3><p>轻巧绿色的本地音乐播放器，颜值也高。<br><img data-src="https://image.ponder.work/mweb/16-7-31/35139480.jpg"><br><strong>下载地址</strong> <a href="http://airplay3.com/classic.html#d1">官方网站</a></p>
<h3 id="QQ影音"><a href="#QQ影音" class="headerlink" title="QQ影音"></a>QQ影音</h3><p>视频播放器，腾讯几款良心作品之一，推荐日常使用。<br><img data-src="https://image.ponder.work/mweb/16-7-31/25331651.jpg"><br><strong>下载地址</strong> <a href="http://player.qq.com/">官方网站</a></p>
<h3 id="PotPlayer"><a href="#PotPlayer" class="headerlink" title="PotPlayer"></a>PotPlayer</h3><p>视频播放器，推荐高级玩家使用。<br><img data-src="https://image.ponder.work/mweb/16-7-31/95138136.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1slvhQzF">安装版 by qiuquan.cc</a></p>
<h2 id="图形图像"><a href="#图形图像" class="headerlink" title="图形图像"></a>图形图像</h2><h3 id="Fast-Stone-Capture"><a href="#Fast-Stone-Capture" class="headerlink" title="Fast Stone Capture"></a>Fast Stone Capture</h3><p>一款截图工具，全屏截图、窗口截图、滚动截图、录屏，一应俱全。<br>本篇文章的图都是用的这个。<br><img data-src="https://image.ponder.work/mweb/16-7-31/40079271.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1eSuiuVC">绿色免安装</a></p>
<h3 id="2345看图王（去广告版）"><a href="#2345看图王（去广告版）" class="headerlink" title="2345看图王（去广告版）"></a>2345看图王（去广告版）</h3><p>看图王功能还是不错的，就是广告恶心。去广告版就完美解决了这个问题，一般人我还不告诉他。<br><img data-src="https://image.ponder.work/mweb/16-7-31/55870339.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1gfyPYZx">去广告安装版 by qiuquan.cc</a></p>
<h3 id="ColorPix"><a href="#ColorPix" class="headerlink" title="ColorPix"></a>ColorPix</h3><p>屏幕取色工具，取得屏幕任意一点的颜色RGB数值。<br><img data-src="https://image.ponder.work/mweb/16-7-31/99699958.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1pLlI50f">绿色免安装</a></p>
<h2 id="压缩解压"><a href="#压缩解压" class="headerlink" title="压缩解压"></a>压缩解压</h2><h3 id="Bandizip"><a href="#Bandizip" class="headerlink" title="Bandizip"></a>Bandizip</h3><p>韩国产压缩解压软件，免费好用，比winrar更为人性化，比如自动解压等等。<br><img data-src="https://image.ponder.work/mweb/16-7-31/27533157.jpg"><br><strong>下载地址</strong> <a href="https://www.bandisoft.com/bandizip/cn/">官方网站</a></p>
<h2 id="学习办公"><a href="#学习办公" class="headerlink" title="学习办公"></a>学习办公</h2><h3 id="ABBYY-FineReader"><a href="#ABBYY-FineReader" class="headerlink" title="ABBYY FineReader"></a>ABBYY FineReader</h3><p>OCR文字识别利器。<br>图片转文字，可处理图片及pdf文档；处理扫描版pdf使之可搜索；屏幕截图转文字。懒人必备。<br><img data-src="https://image.ponder.work/mweb/16-7-31/86205048.jpg"><br><strong>下载地址</strong> <a href="https://pan.baidu.com/s/1cA4W8m">安装版</a></p>
<h3 id="Calibre"><a href="#Calibre" class="headerlink" title="Calibre"></a>Calibre</h3><p>电子书管理工具。<br>可以对各种电子书进行格式转换，比如epub、mobi之前互相转换。也可以作为本地电子书阅读器。<br><img data-src="https://image.ponder.work/mweb/16-7-31/43681912.jpg"><br><strong>下载地址</strong> <a href="https://calibre-ebook.com/download">官方网站</a></p>
<h3 id="File-Locator-Pro"><a href="#File-Locator-Pro" class="headerlink" title="File Locator Pro"></a>File Locator Pro</h3><p>全文搜索工具<br>可以在搜索整个文件夹下的的文件，匹配你想要的文字，写东西的时候查起来甚是方便，旁征博引不是梦。<br><img data-src="https://image.ponder.work/mweb/16-7-31/30225596.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1qYvjQZ6">便携版 by portablesoft.org</a></p>
<h3 id="Foxit-Phantom-PDF"><a href="#Foxit-Phantom-PDF" class="headerlink" title="Foxit Phantom PDF"></a>Foxit Phantom PDF</h3><p>福昕PDF套件，pdf编辑工具，功能不差，但比Adobe Acrobat小巧。<br><img data-src="https://image.ponder.work/mweb/16-7-31/15786165.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1bpFTh3T">绿色免安装</a></p>
<h3 id="Foxit-Reader"><a href="#Foxit-Reader" class="headerlink" title="Foxit Reader"></a>Foxit Reader</h3><p>福昕PDF阅读器，Adobe Reader替代品，绿色小巧。<br><img data-src="https://image.ponder.work/mweb/16-7-31/89045306.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1qXA2IjM">安装版 by repaik.com</a></p>
<h3 id="Foxmail"><a href="#Foxmail" class="headerlink" title="Foxmail"></a>Foxmail</h3><p>应该是windows上最好用的邮件客户端吧，腾讯良心产品之一，张小龙作品。<br><img data-src="https://image.ponder.work/mweb/16-7-31/12469324.jpg"><br><strong>下载地址</strong> <a href="http://www.foxmail.com/">官方网站</a></p>
<h3 id="haroopad"><a href="#haroopad" class="headerlink" title="haroopad"></a>haroopad</h3><p>markdown编辑器，实时预览，比较好用功能也比较全，我博客的文章都是用这个编辑器写的。<br><img data-src="https://image.ponder.work/mweb/16-7-31/28213924.jpg"><br><strong>下载地址</strong> <a href="http://pad.haroopress.com/user.html">官方网站</a></p>
<h3 id="Mindjet-MindManager"><a href="#Mindjet-MindManager" class="headerlink" title="Mindjet MindManager"></a>Mindjet MindManager</h3><p>思维导图工具，众多思维导图工具工具里最好用的一款。<br><img data-src="https://image.ponder.work/mweb/16-7-31/58112268.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1qYCKOBa">绿色便携版 by 米苑</a></p>
<h3 id="Notepad2"><a href="#Notepad2" class="headerlink" title="Notepad2"></a>Notepad2</h3><p>系统记事本完美替代品，小巧功能也不少。<br><img data-src="https://image.ponder.work/mweb/16-7-31/54532759.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1geCMT4f">绿色版 by zd423</a></p>
<h3 id="PdgCntEditor"><a href="#PdgCntEditor" class="headerlink" title="PdgCntEditor"></a>PdgCntEditor</h3><p>pdf文件目录书签编辑器，挺有用的。<br><img data-src="https://image.ponder.work/mweb/16-7-31/21409805.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1eRMswRS">绿色版</a></p>
<h3 id="wps-offcie"><a href="#wps-offcie" class="headerlink" title="wps offcie"></a>wps offcie</h3><p>轻量，轻度使用足矣；如果以操作office为主要工作还是上微软的吧。<br><img data-src="https://image.ponder.work/mweb/16-7-31/63103918.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1qYIk4yC">绿色版 by 未止</a></p>
<h3 id="QQ输入法（传统版）"><a href="#QQ输入法（传统版）" class="headerlink" title="QQ输入法（传统版）"></a>QQ输入法（传统版）</h3><p>腾讯另一款良心产品，无广告比搜狗好多了，配上我做的皮肤，不能再赞。<br><img data-src="https://image.ponder.work/mweb/16-7-31/3849029.jpg"><br><strong>下载地址</strong> <a href="http://qq.pinyin.cn/">官方网站</a> &amp; <a href="http://pan.baidu.com/s/1miexf9e">自制极简皮肤</a></p>
<h3 id="lingoes词霸"><a href="#lingoes词霸" class="headerlink" title="lingoes词霸"></a>lingoes词霸</h3><p>一款词典软件，词典比有道词典丰富，官方支持多种词典，英汉汉英、日语、德语、成语应有尽有。<br><img data-src="https://image.ponder.work/mweb/16-7-31/85433521.jpg"><br><strong>下载地址</strong> <a href="http://www.lingoes.cn/zh/translator/download.htm">官方网站</a> &amp; <a href="http://www.lingoes.cn/zh/dictionary/index.html">词库下载</a></p>
<h3 id="sublime-text-3"><a href="#sublime-text-3" class="headerlink" title="sublime text 3"></a>sublime text 3</h3><p>文本编辑器，写起代码爽到飞起，我每天都用它写Python。<br><img data-src="https://image.ponder.work/mweb/16-7-31/11659988.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1bpJWTXH">自用Python配置</a></p>
<h2 id="上传下载"><a href="#上传下载" class="headerlink" title="上传下载"></a>上传下载</h2><h3 id="百度云管家（绿色版）"><a href="#百度云管家（绿色版）" class="headerlink" title="百度云管家（绿色版）"></a>百度云管家（绿色版）</h3><p>现在的百度网盘不是会员下载龟速，用这款可以无限试用加速，悄悄的拿不要声张。<br><img data-src="https://image.ponder.work/mweb/16-7-31/13558699.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1qXV12K8">密码：441e</a></p>
<h3 id="flvcd硕鼠下载器"><a href="#flvcd硕鼠下载器" class="headerlink" title="flvcd硕鼠下载器"></a>flvcd硕鼠下载器</h3><p>下载视频的好帮手，打开<a href="http://www.flvcd.com/"></a>可以解析优酷、土豆等视频的下载地址。然后调用这个下载器下载视频。<br><img data-src="https://image.ponder.work/mweb/16-7-31/91251768.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1c2eDxmS">便携版下载器</a></p>
<h3 id="迅雷极速版（去广告）"><a href="#迅雷极速版（去广告）" class="headerlink" title="迅雷极速版（去广告）"></a>迅雷极速版（去广告）</h3><p>老板牌下载软件，这就不用说了，贡献一下我在使用的版本。<br><img data-src="https://image.ponder.work/mweb/16-7-31/79061499.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1slw3NNv">安装版 by qiuquan.cc</a></p>
<h2 id="系统工具"><a href="#系统工具" class="headerlink" title="系统工具"></a>系统工具</h2><h3 id="ConEmu"><a href="#ConEmu" class="headerlink" title="ConEmu"></a>ConEmu</h3><p>windows命令行的最佳替代品。</p>
<ul>
<li>多标签</li>
<li>可调字体</li>
<li>可调背景</li>
<li>左右分栏<br><img data-src="https://image.ponder.work/mweb/16-7-31/88953770.jpg"><br><strong>下载地址</strong> <a href="http://www.fosshub.com/ConEmu.html">官方网站</a></li>
</ul>
<h3 id="Crystal-Disk-Info"><a href="#Crystal-Disk-Info" class="headerlink" title="Crystal Disk Info"></a>Crystal Disk Info</h3><p>硬盘信息查看。<br>可看硬盘健康度、温度、通电时间、SSD数据写入量、硬盘SMART数据。<br><img data-src="https://image.ponder.work/mweb/16-7-31/70958830.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1jIvwhlc">绿色版</a></p>
<h3 id="Dism"><a href="#Dism" class="headerlink" title="Dism++"></a>Dism++</h3><p>windows更新清理工具<br>用于解决windows的C盘可用空间越来越小的问题，谨慎操作。<br><img data-src="https://image.ponder.work/mweb/16-7-31/36873914.jpg"><br><strong>下载地址</strong> <a href="https://www.chuyu.me/">官方网站</a></p>
<h3 id="Disk-Genius"><a href="#Disk-Genius" class="headerlink" title="Disk Genius"></a>Disk Genius</h3><p>硬盘分区工具<br>功能强大，数据无价请谨慎操作。</p>
<ul>
<li>数据恢复</li>
<li>分区表备份还原</li>
<li>分区大小调整</li>
<li>硬盘数据备份<br><img data-src="https://image.ponder.work/mweb/16-7-31/4467578.jpg"><br><strong>下载地址</strong> <a href="http://www.diskgenius.cn/download.php">官方网站</a></li>
</ul>
<h3 id="分区助手"><a href="#分区助手" class="headerlink" title="分区助手"></a>分区助手</h3><p>另一款分区工具，操作比较傻瓜，特色功能是无损分区大小调整。注意，如果数据很多的话，调整分区可能要几个小时。<br>数据无价，谨慎操作。<br><img data-src="https://image.ponder.work/mweb/16-7-31/35423482.jpg"><br><strong>下载地址</strong> <a href="http://www.disktool.cn/download.html">官方网站</a></p>
<h3 id="Duplicate-Cleaner"><a href="#Duplicate-Cleaner" class="headerlink" title="Duplicate Cleaner"></a>Duplicate Cleaner</h3><p>重复文件查找工具，解放硬盘空间。<br>可通过多种方式识别重复文件，并且引导你选择删除。<br><img data-src="https://image.ponder.work/mweb/16-7-31/68806305.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1jIfhgua">绿色版</a></p>
<h3 id="金山毒霸垃圾清理（提取版）"><a href="#金山毒霸垃圾清理（提取版）" class="headerlink" title="金山毒霸垃圾清理（提取版）"></a>金山毒霸垃圾清理（提取版）</h3><p>金山毒霸的垃圾清理功能的提取版，只包含垃圾清理功能，其他一概没有。强烈推荐。<br><img data-src="https://image.ponder.work/mweb/16-7-31/28134885.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1c1Aln7U">绿色版 by zd423</a></p>
<h3 id="Vmware-Workstation-11-2"><a href="#Vmware-Workstation-11-2" class="headerlink" title="Vmware Workstation 11.2"></a>Vmware Workstation 11.2</h3><p>虚拟机软件，搭个测试环境什么的很合适。<br>vmware workstation功能强大，就是体积太大。这个版本100m左右，基本功能却一个不少，还要什么自行车。<br><img data-src="https://image.ponder.work/mweb/16-7-31/26911894.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1i5hQYNF">精简安装版 by repaik.com</a></p>
<h3 id="StartItsBack"><a href="#StartItsBack" class="headerlink" title="StartItsBack++"></a>StartItsBack++</h3><p>让windows8&#x2F;10的开始菜单变成win7那种，强烈推荐。<br><img data-src="https://image.ponder.work/mweb/16-7-31/12315390.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1dFdVvxv">安装版 by zd423</a></p>
<h3 id="360杀毒国际版"><a href="#360杀毒国际版" class="headerlink" title="360杀毒国际版"></a>360杀毒国际版</h3><p>跟国内版一比，国际版简直是活雷锋，实在要用360的同学就用国际版吧。<br><img data-src="https://image.ponder.work/mweb/16-7-31/75868311.jpg"><br><strong>下载地址</strong> <a href="https://www.360totalsecurity.com/zh-cn/features/360-total-security-essential/">官方网站</a></p>
<h3 id="GlassWire"><a href="#GlassWire" class="headerlink" title="GlassWire"></a>GlassWire</h3><p>漂亮又实用的windows防火墙，可以用来禁止一些程序联网，统计程序的网络使用情况。<br><img data-src="https://image.ponder.work/mweb/16-7-31/31707316.jpg"><br><strong>下载地址</strong> <a href="https://www.glasswire.com/">官方网站</a></p>
<h3 id="TreeSizeFree"><a href="#TreeSizeFree" class="headerlink" title="TreeSizeFree"></a>TreeSizeFree</h3><p>查看每个文件夹的空间使用情况，删除无用文件，解放硬盘空间。<br><img data-src="https://image.ponder.work/mweb/16-7-31/56508498.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1slqyk2p">绿色版</a></p>
<h3 id="Uninstall-Tool"><a href="#Uninstall-Tool" class="headerlink" title="Uninstall Tool"></a>Uninstall Tool</h3><p>软件卸载工具，简洁好用。<br><img data-src="https://image.ponder.work/mweb/16-7-31/85409752.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1c2um0nu">绿色版 by zd423</a></p>
<h2 id="数据恢复"><a href="#数据恢复" class="headerlink" title="数据恢复"></a>数据恢复</h2><h3 id="EasyRecovery"><a href="#EasyRecovery" class="headerlink" title="EasyRecovery"></a>EasyRecovery</h3><p>误删数据恢复<br>比如手贱误删了数据，或者u盘被格式化了，用这个或许能救命。数据无价，这个不保证成功率，发现数据误删请马上停止对这个硬盘的写入操作，并用这个软件试试。<br><img data-src="https://image.ponder.work/mweb/16-7-31/35231352.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1kU5TkxD">便携版</a></p>
<h3 id="Finaldata"><a href="#Finaldata" class="headerlink" title="Finaldata"></a>Finaldata</h3><p>另一款数据恢复软件<br><img data-src="https://image.ponder.work/mweb/16-7-31/91301291.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1bpysnvx">绿色版</a></p>
<h3 id="7-Data"><a href="#7-Data" class="headerlink" title="7-Data"></a>7-Data</h3><p>又一款数据恢复软件，比较适用于误删除恢复。<br><img data-src="https://image.ponder.work/mweb/16-7-31/43581446.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1slqyk3r">绿色版 by zd423</a></p>
<h2 id="效率工具"><a href="#效率工具" class="headerlink" title="效率工具"></a>效率工具</h2><h3 id="Everything"><a href="#Everything" class="headerlink" title="Everything"></a>Everything</h3><p>文件快速搜索工具<br>比系统自带搜索快N倍，什么东西都给你搜出来，藏小黄片的可小心了，科科。<br><img data-src="https://image.ponder.work/mweb/16-7-31/76545715.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1slF0AVb">绿色版 by zd423</a></p>
<h3 id="Wox"><a href="#Wox" class="headerlink" title="Wox"></a>Wox</h3><p>软件快速启动&amp;文件搜索工具，适合我这种软件特别多的，让你再也不会在杂乱的桌面上找图标。<br><img data-src="https://image.ponder.work/mweb/16-7-31/54419094.jpg"><br><strong>下载地址</strong> <a href="https://github.com/Wox-launcher/Wox/releases">官方网站</a></p>
<h2 id="其它应用"><a href="#其它应用" class="headerlink" title="其它应用"></a>其它应用</h2><h3 id="Clover"><a href="#Clover" class="headerlink" title="Clover"></a>Clover</h3><p>给windows的资源管理器加上标签页，用起来就像chrome浏览器<br><img data-src="https://image.ponder.work/mweb/16-7-31/57796893.jpg"><br><strong>下载地址</strong> <a href="http://cn.ejie.me/">官方网站</a></p>
<h3 id="Beyond-Compare"><a href="#Beyond-Compare" class="headerlink" title="Beyond Compare"></a>Beyond Compare</h3><p>文本、文件夹比较工具，比较他们之间的不同之处。<br><img data-src="https://image.ponder.work/mweb/16-7-31/99742922.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1dE2HBaD">绿色版 by zd423</a></p>
<h3 id="DragKing"><a href="#DragKing" class="headerlink" title="DragKing"></a>DragKing</h3><p>选中文本自动复制到剪贴板。<br><img data-src="https://image.ponder.work/mweb/16-7-31/64811068.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1bRQmhK">绿色版</a></p>
<h3 id="Glass2k"><a href="#Glass2k" class="headerlink" title="Glass2k"></a>Glass2k</h3><p>可以让所有的windows程序都透明，透明度可选。<br><img data-src="https://image.ponder.work/mweb/16-7-31/76626295.jpg"><br><strong>下载地址</strong> [<a href="https://pan.baidu.com/s/1dFhtk1j]">https://pan.baidu.com/s/1dFhtk1j]</a>(链接: <a href="https://pan.baidu.com/s/1dFhtk1j">https://pan.baidu.com/s/1dFhtk1j</a> 密码: vg6r) 密码: vg6r</p>
<h3 id="f-lux"><a href="#f-lux" class="headerlink" title="f.lux"></a>f.lux</h3><p>调节屏幕色温，相当于安卓手机上的护眼模式。<br><img data-src="https://image.ponder.work/mweb/16-7-31/29430071.jpg"><br><strong>下载地址</strong> <a href="https://justgetflux.com/">官方网站</a></p>
<h3 id="LastActivityView"><a href="#LastActivityView" class="headerlink" title="LastActivityView"></a>LastActivityView</h3><p>查看电脑的操作记录，可以看看你平时用电脑都在干什么，或者别人平时在用你的电脑干什么，科科。<br><img data-src="https://image.ponder.work/mweb/16-7-31/32925079.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1nvk0FrF">绿色版</a></p>
<h3 id="Top-总在最前"><a href="#Top-总在最前" class="headerlink" title="Top 总在最前"></a>Top 总在最前</h3><p>可以让每一个窗口都可以在最前，一边查资料一边写东西很合适。<br>使用方法：将软件的“+”字拖动到你需要置顶的窗口的标题栏上。<br><img data-src="https://image.ponder.work/mweb/16-7-31/71162085.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1nv8hl8H">绿色版</a></p>
<h3 id="WindowTabs"><a href="#WindowTabs" class="headerlink" title="WindowTabs"></a>WindowTabs</h3><p>让每一款软件都拥有多标签模式，适合疯狂开窗口的人，比如我。<br><img data-src="https://image.ponder.work/mweb/16-7-31/64210838.jpg"><br><strong>下载地址</strong> <a href="http://pan.baidu.com/s/1mi51yBm">绿色版 by 大眼仔</a></p>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>软件</tag>
      </tags>
  </entry>
  <entry>
    <title>xgboost C++线程安全问题定位与修复</title>
    <url>/2021/03/25/xgboost-multi-thread-problem-debug-and-fix/</url>
    <content><![CDATA[<p>公司的线上模型服务是基于<code>brpc + xgboost</code>实现的，而xgboost官方是不支持在多线程环境下使用的（1.2.0版本之前）</p>
<p>这个模型服务已经有两年多了，显然当时用的版本是不支持多线程的，有位同事当时修改了xgboost的源码，解决了多线程的问题，在线上也稳定运行到现在。</p>
<p>那么，问题来了。最近有个新需求，用到了xgboost的<code>pred_leaf</code>功能，然后就发现并发请求时<code>0.1%</code>的模型结果不对。</p>
<span id="more"></span>

<h2 id="分析问题"><a href="#分析问题" class="headerlink" title="分析问题"></a>分析问题</h2><p>调试过程中，首先删除其他模型，排除干扰。然后开关<code>pred_leaf</code>功能批量对比测试，确认当该功能关闭时，模型结果是正常的。</p>
<p>所以，很有可能<code>pred_leaf</code>参数导致程序走到和之前不同的分支，而这个分支的多线程问题并未修复。</p>
<p>那么，解决问题的思路就是：确定之前同事修改了什么，找到与pred_leaf相关的函数，尝试做相同修复。</p>
<h2 id="尝试解决"><a href="#尝试解决" class="headerlink" title="尝试解决"></a>尝试解决</h2><p>首先得确定同事都修改了什么，由于代码历史比较就远了了，而且xgboost的改动并没有加入到git，也没有明显的版本号等标识，这个地方也就比较头疼了。</p>
<p>唯一能确定的是，xgboost版本的是0.6左右，修改源码时参考了<a href="https://blog.csdn.net/zc02051126/article/details/79427605">文章</a>。</p>
<p>所以只能从git下载最新的代码，然后<code>git --no-pager log  --stat</code>查看每个commit的改动情况，使用<code>beyond compared对比 + 人肉二分查找</code>，最终定位到对应的commit。</p>
<p>找到了对应的commit之后，通过对比改动和结合上文参考文章，其实就是两类改动。</p>
<ol>
<li>存在竞争条件的变量 -&gt; 加锁</li>
<li>不存在竞争条件的变量 -&gt; threadLocal，栈上自动变量</li>
</ol>
<p>具体来说，以<code>PredLoopSpecalize</code>为例， 修改前</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">PredLoopSpecalize</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> nthread = <span class="built_in">omp_get_max_threads</span>();</span><br><span class="line">    <span class="built_in">InitThreadTemp</span>(nthread, model.param.num_feature);</span><br><span class="line">    <span class="keyword">for</span> (bst_omp_uint i = <span class="number">0</span>; i &lt; nsize - rest; i += K) &#123;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> tid = <span class="built_in">omp_get_thread_num</span>();</span><br><span class="line">        RegTree::FVec&amp; feats = thread_temp[tid];</span><br><span class="line">        <span class="comment">// thread_temp 为成员变量</span></span><br><span class="line">        <span class="comment">// 省略其他逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">InitThreadTemp</span><span class="params">(<span class="type">int</span> nthread, <span class="type">int</span> num_feature)</span> </span>&#123;</span><br><span class="line">    <span class="type">int</span> prev_thread_temp_size = thread_temp.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">if</span> (prev_thread_temp_size &lt; nthread) &#123;</span><br><span class="line">      thread_temp.<span class="built_in">resize</span>(nthread, RegTree::<span class="built_in">FVec</span>());</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> i = prev_thread_temp_size; i &lt; nthread; ++i) &#123;</span><br><span class="line">        thread_temp[i].<span class="built_in">Init</span>(num_feature);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>修改后</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">PredLoopSpecalize</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">int</span> nthread = <span class="built_in">omp_get_max_threads</span>();</span><br><span class="line">    std::vector&lt;RegTree::FVec&gt; local_thread_temp;  <span class="comment">// 改动点</span></span><br><span class="line">    <span class="type">int</span> prev_thread_temp_size = local_thread_temp.<span class="built_in">size</span>();</span><br><span class="line">    <span class="keyword">if</span> (prev_thread_temp_size &lt; nthread) &#123;</span><br><span class="line">      local_thread_temp.<span class="built_in">resize</span>(nthread, RegTree::<span class="built_in">FVec</span>());</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> i = prev_thread_temp_size; i &lt; nthread; ++i) &#123;</span><br><span class="line">        local_thread_temp[i].<span class="built_in">Init</span>(model.param.num_feature);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (bst_omp_uint i = <span class="number">0</span>; i &lt; nsize - rest; i += K) &#123;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> tid = <span class="built_in">omp_get_thread_num</span>();</span><br><span class="line">        RegTree::FVec&amp; feats = local_thread_temp[tid];  <span class="comment">// 改动点</span></span><br><span class="line">        <span class="comment">// thread_temp 为成员变量</span></span><br><span class="line">        <span class="comment">// 省略其他逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>显然问题就在<code>thread_temp</code>, 做为类成员，它不是线程安全的，通过替换为栈上的<code>local_thread_temp</code>，不同进程访问的地址不同，自然就不存在冲突了。</p>
<p>通过查找<code>PredLoopSpecalize</code>函数的调用链，可以发现其修改只会影响正常预测，pred_leaf则是不同的分支，显然线程安全的问题依然存在。</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 调用链</span></span><br><span class="line"><span class="built_in">XGBoosterPredict</span>()</span><br><span class="line">    --&gt; LearnerImpl::<span class="built_in">Predict</span>()</span><br><span class="line">        --&gt; LearnerImpl::<span class="built_in">PredictRaw</span>() <span class="comment">// if 正常predict</span></span><br><span class="line">            --&gt; GBTree::<span class="built_in">PredictBatch</span>()</span><br><span class="line">                --&gt; CPUPredictor::<span class="built_in">PredictBatch</span>()</span><br><span class="line">                    --&gt; CPUPredictor::<span class="built_in">PredLoopInternal</span>()</span><br><span class="line">                        --&gt; Dart::<span class="built_in">PredLoopSpecalize</span>()</span><br><span class="line">        --&gt; GBTree::<span class="built_in">PredictLeaf</span>()   <span class="comment">// if pred_leaf</span></span><br><span class="line">        --&gt; GBTree::<span class="built_in">PredictContribution</span>()</span><br><span class="line">        --&gt; ObjFunction::<span class="built_in">PredTransform</span>()</span><br><span class="line"></span><br><span class="line"><span class="comment">// LearnerImpl::Predict() 源码</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Predict</span><span class="params">(DMatrix* data, <span class="type">bool</span> output_margin,</span></span></span><br><span class="line"><span class="params"><span class="function">            std::vector&lt;bst_float&gt;* out_preds, <span class="type">unsigned</span> ntree_limit,</span></span></span><br><span class="line"><span class="params"><span class="function">            <span class="type">bool</span> pred_leaf, <span class="type">bool</span> pred_contribs)</span> <span class="type">const</span> <span class="keyword">override</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (pred_contribs) &#123;</span><br><span class="line">        gbm_-&gt;<span class="built_in">PredictContribution</span>(data, out_preds, ntree_limit);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pred_leaf) &#123;</span><br><span class="line">        gbm_-&gt;<span class="built_in">PredictLeaf</span>(data, out_preds, ntree_limit);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>-&gt;<span class="built_in">PredictRaw</span>(data, out_preds, ntree_limit);</span><br><span class="line">        <span class="keyword">if</span> (!output_margin) &#123;</span><br><span class="line">        obj_-&gt;<span class="built_in">PredTransform</span>(out_preds);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>所以，最后采用与<code>PredLoopSpecalize</code>相同方法对<code>PredictLeaf</code>进行修复，问题得以完美解决。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol>
<li>源码版本管理非常重要。</li>
<li>软件版本在新建项目时尽量选择较新版本，因为后续迭代更新版本的概率较小。</li>
<li>xgboost建议使用1.2.0以上版本，已经线程安全。</li>
</ol>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://blog.csdn.net/zc02051126/article/details/79427605">https://blog.csdn.net/zc02051126/article/details/79427605</a></li>
<li><a href="https://github.com/dmlc/xgboost">https://github.com/dmlc/xgboost</a></li>
</ul>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
        <tag>机器学习</tag>
        <tag>C++</tag>
      </tags>
  </entry>
  <entry>
    <title>《一九八四》读书笔记</title>
    <url>/2015/10/17/%E3%80%8A%E4%B8%80%E4%B9%9D%E5%85%AB%E5%9B%9B%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><ul>
<li><p>简介：政治寓言，可惜在过往历史中已经能找到他的影子了，老大哥、真理部等梗的来源。说的是在1984年世界被三个国家瓜分，他们均高度集权，改变历史，改变语言（所谓新话）；建立健全的监控系统，控制人们的思想和行为。</p>
</li>
<li><p>本篇文章仅为测试人工智能的最新研究进展。</p>
</li>
</ul>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><ul>
<li><p>他们因为不能去看绞刑，所以不高兴。<br>  博主云：他人为敌国，很多时候你的痛苦在别人眼中什么都不是。当然，反过来也是成立的。<br>  古人云：我死后，哪管洪水滔天。</p>
</li>
<li><p>所有的历史都是可以多次重新书写的本子，只要需要可以随时擦干净重新书写。<br>  博主云：清朝的愚民是不让你明白，而更高明的是只给你简单的无可证伪的假信息，因为当世界只存在一种东西时就无所谓真假了。</p>
</li>
<li><p>性压抑能导致歇斯底里，这是求之不得的，因为它能转化成战争狂热和对领袖的崇拜。</p>
</li>
<li><p>一个等级社会只有建立在贫穷和无知的基础上才可能存在。<br>  朝鲜：你们慢慢聊，我先走了。</p>
</li>
<li><p>通过承诺在死后可以进入一个想象出来的世界，从而淡化等级的压迫性。<br>  博主云：正因如此，佛家产生在等级制度最为森严的印度。此生遭罪，为前世造业；今生修福，为投生净土。<br>  ISIS：招募“圣战士” 死后能获72位纯洁处女。</p>
</li>
<li><p>统治集团下台可能的情况：被征服；统治效率低，被造反；让强大的充满不满的中等阶级集团出现；或者是自己丧失了统治的信心和意志。这四者一般综合起来作用。</p>
</li>
<li><p>大众只要不让他们掌握比较的标准，他们就甚至永远不会意识到他们在受压迫。<br>  博主云：比较让我们变得不幸福。在不知道一块钱一斤的苹果之前，十块钱一斤的苹果是很便宜的。</p>
</li>
<li><p>寡头统治的要旨不是父传子，子传孙，而是坚持死者加诸生者的某种世界观和生活方式。只要他能指派自己的后继者，统治集团就永远回事统治集团。它所关心的不是血统上的永存，而是自身的不朽。只要等级化结构永远保持不变，至于是谁掌握权力并不重要。</p>
</li>
</ul>
<h2 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h2><ul>
<li><a href="http://pan.baidu.com/s/1hq8RADI">百度网盘</a></li>
</ul>
<h2 id="牢记"><a href="#牢记" class="headerlink" title="牢记"></a>牢记</h2><blockquote>
<p><strong>Big brother is watching you !</strong></p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《七十年代》读书笔记</title>
    <url>/2016/08/17/%E3%80%8A%E4%B8%83%E5%8D%81%E5%B9%B4%E4%BB%A3%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>作者：北岛、李陀</p>
<p>最早识得北岛这个作家，是因为他的诗句，并为之倾倒。</p>
<pre><code>卑鄙是卑鄙者的通行证，
高尚是高尚者的墓志铭，
看吧，在那镀金的天空中，
飘满了死者弯曲的倒影。
</code></pre>
<p>这本书收集了三十篇記憶文字，是作者们对于”上世纪70年代”这一段特殊历史时期的印象和回忆。</p>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>不知是因为年轻还是饥饿，我们几乎每时每刻低头觅食，仿佛猪狗。</p>
<blockquote>
<p>阮云：我们难以想象的事，却以极平淡的语句说出，让人更加难以承受。</p>
</blockquote>
<p>历史在原来作痛的地点消失了。</p>
<p>这个世界还会好吗，梁漱溟临终前的话。</p>
<blockquote>
<p>阮云：这世界曾经好过吗，其实它从未真正好过，只是有时我们居住在它稍微好一点的那部分里。</p>
</blockquote>
<p>大家极力地忘却七十年代，以为这样它就未发生过。</p>
<blockquote>
<p>阮云：人遇到痛苦的事，为了让自己好受一点，会不自觉地忘却它。这正是人的优点之一，然而也正是可悲之处。当痛苦的承受者都忘却了，痛苦的制造者会怎么想呢？</p>
</blockquote>
<p>普通百姓的日常生活，才是被历史忽略的最重要的部分。</p>
<blockquote>
<p>阮云：你我百年之后，世上还有几许痕迹。</p>
</blockquote>
<p>读啊写啊，可这都什么年代了，可别玩枪口上撞。</p>
<blockquote>
<p>阮云：读书人呐，过于理想乃至于幼稚。毛主席为啥读资治通鉴十七遍，而不是其他。</p>
</blockquote>
<p>历史永远记录的是大人物的大事，而小人物的日常感受我们无从得知。</p>
<p>现在，大家笑朝鲜，那可真是“好了伤疤忘了疼”，小孩不知道也就算了，大人也一般傻。</p>
<p>政治面貌调查和元朝人分四等有何区别。</p>
<blockquote>
<p>阮云：在性质上有区别，一个是伟光正的社会主义的，一个是腐朽的资本主义的。</p>
</blockquote>
<p>小桥报有人痴立，泪撒春帘一饼茶 —— 《己亥杂诗》龚自珍</p>
<p>谁复留君住，叹人生几番离合，便成迟暮。—— 《金缕曲》纳兰性德</p>
<blockquote>
<p>阮云：何须几番离合，一见杨过便已把终身误了。</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《乡关何处》读书笔记</title>
    <url>/2015/09/29/%E3%80%8A%E4%B9%A1%E5%85%B3%E4%BD%95%E5%A4%84%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><ul>
<li>这本书是野夫关于他个人对几十年前那动荡而又荒诞不经的时代的感悟，真实的过去往往没有历史书上写的那么伟光正、那么云淡风轻。</li>
<li>抑郁症、愤青不可读此书，恐加重病情。</li>
</ul>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><ol>
<li><p>事实上，组织中只有细胞，是不再有人的。发明组织的人，是按机器原理设计的，个体的人在组织中，类似某个螺丝、刀片一般的部件。任何个人主义和自由主义，都是组织所不允许的；组织只会冠冕堂皇地提倡集体主义，会用无数教条来帮助你遗忘作为人的个性。</p>
</li>
<li><p>我们那一代人，许多是真正的理想主义者，而蒋介石从孙中山那里继承而来的国家体制，是违背现代宪政的“三一律”——一个领袖，一个主义，一个政党。当基本的人权都要被这个政府所钳制时，如果有另外一个党打出“要自由民主，要结社言论自由”的口号时，你说它能不吸引我们这些爱国哀民而又轻身躁进企图改造社会的理想青年吗？<br> 博主云：所以我说从来不看好学生运动的，改造社会这种事情从来不是光靠满腔热情所能做到的，作为领袖没有一点的手腕是很难成就的。而那些政客更不用说了，他们永远是利益至上的，不知理想为何物。最好的状态是有个理想残存的野心家，然而毕竟是奢望。</p>
</li>
<li><p>生命不是话剧，可以彩排一次再正式登台。他们的悲剧一次性上演，就挥霍完了他们的一生。<br> 博主云：我们从未踏入同一条河流，人生之无奈正出于此。</p>
</li>
<li><p>迅翁尝云：人最痛苦的莫过于梦醒之后无路可走。于我，则常是中宵酒醒之后，无路可走而深陷回忆，牵出无数往事的余痛。</p>
</li>
<li><p>老李认为——红卫兵的造反初衷源于那一代的神圣使命感，他们并不单纯，至少不像我们今天想象的那么幼稚。但动机不错的行动并不能保证结果的正确。他认为，知识分子应该为此首先承担罪责，全国各地的“文革”之火并非文盲引起，主要的“纵火犯”都是书生，他们只是没想到“革命最后会革到自己头上”。而在此之前，整个知识群体的道义缺失，客观上默许和纵容了暴政的为所欲为</p>
</li>
<li><p>捡粪的孩子多，牲口的遗矢有限，便不免有人终日碌碌而仅仅捡得几十粒羊矢。也有的持之以恒地跟踪一群牛，焦急地守望牛翘起尾巴，端起撮箕去抢接，甚至为此掀起混战。<br> 博主云：现在看来甚是可笑，但这就是真实的生活，唏嘘！</p>
</li>
<li><p>他说世界上总有一些走投无路的人，需要花点小钱买个安慰；而我们这些废人也是生灵，也要活命，这叫天生人必养人。</p>
</li>
</ol>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《传习录》读书笔记</title>
    <url>/2019/10/07/%E3%80%8A%E4%BC%A0%E4%B9%A0%E5%BD%95%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-10-09-15706298114022.jpg"></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>《传习录》是阳明先生的问答语录和论学书信集。是一部儒家简明而有代表性的哲学著作。</p>
<p>王阳明可贵的不是他的学说，而是他行动和思想一样地伟大，能够知行合一，这样三不朽是理所应当的。</p>
<p>感悟：心学的核心致良知，就是良知的自我表达，良知的自知，主体性的向外扩展，使主观世界和客观世界融为一体，而不是主体被客观世界异化。</p>
<p>愿永为阳明先生门下走狗</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>悬想何益？但不忘栽培之功，怕没有枝叶花实</p>
<p>私欲日生，如地上尘，一日不扫，便有一层</p>
<blockquote>
<p>修行如行路，走得一段便识得一段。若心思不在上面，便是千遍，也未必能识。</p>
</blockquote>
<p>专涵养者，日见其不足；专见识者，日见其有余</p>
<p>狗若不咬人而追石块，终将一无所得。</p>
<blockquote>
<p>譬如以手指月，不看月而追指，终无所得</p>
</blockquote>
<p>夫道必体而后见，非己见道而加体道之功也</p>
<p>夫，正心、诚意、致知、格物，皆所以修身</p>
<p>夫道，天下之公道也；学，天下之公学也。非朱子可得而私也，非孔子可得而私也</p>
<blockquote>
<p>现今流传于世的儒家经典，都夹杂了很多人的私货，尤其是朱熹的。</p>
</blockquote>
<p>勿忘勿助，必有事焉</p>
<blockquote>
<p>不要懈怠也不要冒进，一直修持</p>
</blockquote>
<p>后世学者博文多识，留滞胸中，皆伤食只病也</p>
<blockquote>
<p>学习如吃饭，只一味增加知识而不理解化用，只会消化不良</p>
</blockquote>
<p>人若矜持太过，终是有弊。人只有许多精神，若专在容貌上用功，则于中心照管不及者多矣</p>
<blockquote>
<p>然则，今之世人，类此者不知凡几</p>
</blockquote>
<p>物来则应，不要着一分意思</p>
<blockquote>
<p>道家谓之自然，佛家谓之当下</p>
</blockquote>
<p>读书作文，安能累人？人自累于得失耳。</p>
<p>孟子不论心之动与不动，只是集义（让行为符合义），所行无不是义，此心自然无可动处。</p>
<blockquote>
<p>譬如救火，扬汤止沸和釜底抽薪的区别</p>
</blockquote>
<p>无善无恶心之体，有善有恶意之动，知善知恶是良知，为善去恶是格物。</p>
<p>思与学作两事做，故有‘罔’和‘殆’之病</p>
<p>心犹镜也，圣人心如明镜，常人心如昏镜。近世‘格物’之说，如以镜照物，照上用功，不知镜尚昏在，何能照？先生之‘格物’，如磨镜而使之明，磨上用功，明了后亦未尝废照。</p>
<blockquote>
<p>如何磨镜，于意动处磨镜，即佛家所说之关照己身</p>
</blockquote>
<p>动念处，正心诚意</p>
<blockquote>
<p>譬如，道路弯了要修正，肯定得是在修路的时候去改方向，不然全无用处</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心学</tag>
        <tag>王阳明</tag>
      </tags>
  </entry>
  <entry>
    <title>《冬吴相对论》读书笔记</title>
    <url>/2019/12/16/%E3%80%8A%E5%86%AC%E5%90%B4%E7%9B%B8%E5%AF%B9%E8%AE%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-12-16-15765097203009.jpg"></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>《冬吴相对论》是一档商业脱口秀广播节目，节目由原凤凰卫视著名主持人梁冬与《21世纪商业评论》发行人吴伯凡共同主持。</p>
<p>本书就是节目的内容集结而成，对于经济知识的扫盲有些意义，但也有电视节目的通病，知识流于细碎，没有体系。</p>
<p>初学者用来初步了解经济学现象，还是很有意义的。</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p><em>读得实在是太久了，很多点都忘记了</em></p>
<p>物化，比如乔布斯作为苹果的教主</p>
<blockquote>
<p>阮云：这个词有些难理解，我觉得就是以物（苹果）为主体，人反而是依附</p>
</blockquote>
<p>KISS，keep it simple stupid</p>
<blockquote>
<p>阮云：然而现实却是相反，很多是人为增加复杂度</p>
</blockquote>
<p>杠杆解，症状解</p>
<blockquote>
<p>阮云：症状解就是头痛医头，脚痛医脚。杠杆解就是釜底抽薪，像杠杆一样四两拨千斤的解法。</p>
</blockquote>
<p>领导者与管理者的区别</p>
<ol>
<li>确定什么样的事是值得做的</li>
<li>考虑如何促使它发生</li>
</ol>
<p>品牌是瞬间联想或者叫瞬间认知</p>
<blockquote>
<p>阮云：其实就是潜意识</p>
</blockquote>
<p>管理学，第五级领导</p>
<p>公共成本</p>
<p>智慧就是敬畏，没有敬畏就没有智慧。</p>
<blockquote>
<p>阮云：所谓敬畏，就是有所不为</p>
</blockquote>
<p>奢侈品是一种现代拜物教，被赋予超乎寻常的感情诉求的会有身份标志的一种产品</p>
<blockquote>
<p>阮云：比如前些年的苹果产品</p>
</blockquote>
<p>特权的溢价</p>
<blockquote>
<p>阮云：商品被作为特权的标志而产生的溢价</p>
</blockquote>
<p>不要贪迷于技术的变革</p>
<p>重要的事情是不讲效率的。</p>
<blockquote>
<p>阮云：什么叫重要，其实就是产出投入比高</p>
</blockquote>
<p>自我实现的预言</p>
<blockquote>
<p>阮云：有了锤子，看啥都像钉子</p>
</blockquote>
<p>动机的不对称，混饭吃和搏命的区别</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>随感</tag>
      </tags>
  </entry>
  <entry>
    <title>《十万小时天才理论》摘录</title>
    <url>/2020/03/29/%E3%80%8A%E5%8D%81%E4%B8%87%E5%B0%8F%E6%97%B6%E5%A4%A9%E6%89%8D%E7%90%86%E8%AE%BA%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em></p>
<p>精深练习：朝着既定目标挣扎前进，挑战自己的能力极限，不断放错，这让你更聪明</p>
<p>设定一个稍微超过自己现有能力的目标，盲目受挫毫无帮助，实现目标才能突破原有水平。</p>
<p>专业选手与普通人之间的差别是懂这种语言与不懂的区别</p>
<span id="more"></span>

<p>精深练习</p>
<ol>
<li>组块化：整体了解；分解成基础组块；慢动作练习，加速，了解内在结构</li>
<li>重复练习</li>
<li>尝试体会</li>
</ol>
<p>练习就是每天弹奏同样的音符，人类基本的姿态 —— 为一个想法努力，为你渴望的伟大成就努力争取，然后感觉它与你失之交臂。</p>
<p>激情的工作原理：是那些让我们意识到“我就想成为那样的人”的时刻。</p>
<p>激情的原始信号：未来归属感；不活不再安全；稀缺性</p>
<p>激情不是为遵守规则而存在的，它的存在只是为了工作，为了给自己选择的任务提供能量。</p>
<p>人才温床：肯定努力的价值，以及缓慢的进步，而不是天生的才华和智慧。</p>
<p>良好的性格是在外而内的，而且可以由动机和练习综合创造。</p>
<p><strong>不要期望一下子取得大幅度的进步</strong>，试着每天进步一小点，这是必经之路，而一旦开始进步，就会持续进步。</p>
<p>髓鞘质回路不仅需要精深练习，也需要激情。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《历史深处的忧虑》摘录</title>
    <url>/2020/05/02/%E3%80%8A%E5%8E%86%E5%8F%B2%E6%B7%B1%E5%A4%84%E7%9A%84%E5%BF%A7%E8%99%91%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em></p>
<p>如果你追求阳光，你就躲不开身后的阴影。</p>
<span id="more"></span>

<p>美国青年在刷盘子时不会因为自己屈才而痛感命运不公，怨天怨地</p>
<p>华裔看不起黑人，常常只是因为黑人比他们穷；讨厌犹太人，只是嫉妒犹太人比他们更有成就感。</p>
<p>政府的正当权利，是要经过被治理者的同意才产生的。</p>
<p>必须听那些听不下去的话，“这正是我们必须为自由支付的代价”。</p>
<p>如果你因为害怕一个不自由的时代，因此就不给他们言论自由的话，那么这个不自由的时代已经开始了。</p>
<p>秩序？（没有自由的秩序不是真正的秩序）</p>
<p>安全还是自由，美国每天都在面对新的选择</p>
<blockquote>
<p>阮云：自由美利坚，枪击每一天</p>
</blockquote>
<p>抢不是一种工具，枪是一种权利。</p>
<p>权力会导致腐败，绝对的权力导致绝对的腐败。</p>
<p>一个人的房子就像他的城堡，当他安安静静地等待在里头的时候，他就应该安全得像一个城堡里的王子。</p>
<p>美国最高法院裁定，根据种族而给予优惠，几乎是与宪法精神不符的。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《历史的先声》读书笔记</title>
    <url>/2020/05/04/%E3%80%8A%E5%8E%86%E5%8F%B2%E7%9A%84%E5%85%88%E5%A3%B0%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>记得当时在学校图书馆借了这本书，对幼稚的三观产生了巨大冲击。</p>
<p>书中所记内容，像是渣男语录集结，而纯情少女们也正吃这套。为何呢？漂亮话总是容易说的，成本也小，当你说骚话撩妹的时候，肯定不会想着如何兑现，先撩到手再说。</p>
<p>然而承诺就截然不同了，每一个承诺都得考虑其可行性及付出的成本，自然是得审慎，也就没那么吸引人了。</p>
<p>最终，还是还是白给了渣男的的甜言蜜语。</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>把一党专政化一下妆，当做民主的代用品，方法虽然巧妙，然而和人民的愿望相去十万八千里。中国人民都在睁着眼睛看：不要拿民主的代用品来欺骗我们啊？  – 《新华日报》1945年1月28日</p>
<!-- > 阮云：共宣传民主无非是向国民党说：“你该下台了，轮到我了”，至于后面的事情，哪管那么多。-->
<blockquote>
<p>阮云：政客的话听听就好，但不要往心里去，政客的一切行为都应以动机论来分析</p>
</blockquote>
<blockquote>
<p>阮云：历史，特别是中国近代史，很少能分出个是非曲直，基本上都是屁股决定脑袋的事。</p>
</blockquote>
<p>学校受外力干涉，教学内容受党化思想的规范都是有害的事情。  – 《新华日报》1946年2月6日社论</p>
<blockquote>
<p>阮云：当时国人并未真正了解民主与自由，或者不想去了解，他们只是为了实现自己的目标而谈民主自由，而这个目标恰恰需要一个响亮的口号而已。</p>
</blockquote>
<p>英美民主国家的人民集会，结社，是无论性质、地点、参与者的职业、性别几何，事前均无须请求警察许可，亦无须报告警察。在我国，各种人民团体之成立，无论下级团体或者上级团体，均应先经政府许可。</p>
<p>除了汉奸和反动派，其他任何人，都有说话的自由。</p>
<blockquote>
<p>阮云：汉奸和反动派难度就没有说话自由吗？又是由谁来给这两者盖棺定论呢</p>
</blockquote>
<p>即使说错话了也是不要紧的，国事是国家的公事，不是一党一派的私事。 – 毛泽东《在陕甘宁边区参议会的演讲》</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《变态心理学》摘录</title>
    <url>/2020/03/21/%E3%80%8A%E5%8F%98%E6%80%81%E5%BF%83%E7%90%86%E5%AD%A6%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>补录，笔记太过久远，原文已然忘记。</em></p>
<p>心理变态定义所遵循的标准：</p>
<ul>
<li>与统计标准间的背离；</li>
<li>不适应；</li>
<li>个人感到焦虑；</li>
<li>社会接受程度</li>
</ul>
<span id="more"></span> 

<p>如果你的行为使你能够缓解压力，应对挑战者或者完成你的任务，可以把你的行为称为适应性或者功能性的行为。</p>
<p>人民该怎样做才能使自己的生活变得有意义</p>
<p>文化的重要特征：这种文化的多数成员对什么才是生命中最重要的东西所持有的共同看法，以及这种共同看法的美好生活具有的意义。</p>
<p>一个人是否患有某种精神障碍很大程度上取决于他所面临的社会环境，当他不能正确地适应时就容易发生精神障碍。</p>
<p>何为正常，这需要有在一定环境条件下的统计数据</p>
<p>行为主义者认为焦虑是因为他们把某个并不能引起焦虑的东西与恐惧联系起来</p>
<p>行为理论理解的强迫性行为：未知事物 -&gt; 焦虑不安 -&gt; 某种行动（已知结果） -&gt; 焦虑减轻 -&gt; 强迫形成并加强</p>
<p>人为障碍症：动机在于使病人承担责任，而这样做可以引起社会的关注，并改善病人的社会关系</p>
<p>马斯洛需求理论：生理 -&gt; 安全 -&gt; 社交 -&gt; 尊重 -&gt; 自我实现</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《可乐牛奶经济学3》摘录</title>
    <url>/2020/03/29/%E3%80%8A%E5%8F%AF%E4%B9%90%E7%89%9B%E5%A5%B6%E7%BB%8F%E6%B5%8E%E5%AD%A63%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记, 该书是通俗的经济学读物</em></p>
<p>若奖励主要取决于绝对绩效，个人选择的确有极高效率，可是若奖励取决于相对绩效，看不见的手就会失效。</p>
<p>负外部性：你收益的成本由别人承担（常使看不见的手失效）</p>
<span id="more"></span>

<p>理性的绑匪的需求来自受害者家人的支付能力</p>
<p>倘若商品的价格低于成本，人们就会浪费。</p>
<p>一旦文化流行到某个限度，没有这件事会带来明显的社会成本。</p>
<p>数百万人口的国家平均分配国民收入，意味着一个人不工作生活水准不会下跌，最后所有人都不工作了。</p>
<p>人们觉得自己需要怎样的房子，取决于周围的人有什么样的房子</p>
<blockquote>
<p>阮云：互联网拓宽了周围的范围</p>
</blockquote>
<p>瀑布效应：自己有钱，显得别人穷了</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>经济学</tag>
      </tags>
  </entry>
  <entry>
    <title>《可乐牛奶经济学》摘录</title>
    <url>/2020/03/29/%E3%80%8A%E5%8F%AF%E4%B9%90%E7%89%9B%E5%A5%B6%E7%BB%8F%E6%B5%8E%E5%AD%A6%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记, 该书是通俗的经济学读物</em></p>
<p>“让钱花得物有所值”，只应该发生在交易之前</p>
<p>只要边际收益大于边际成本，我们就应该提高进行此事的程度</p>
<p>自愿前提下发生的交换活动会让所有的参与者生活质量变好。</p>
<span id="more"></span>

<p>在经济活动中药充分利用信息不对称</p>
<p>人们对消费品的适应性比人生体验要快得多</p>
<blockquote>
<p>阮云：失败是有概率的，但当失败发生在你身上时，对于你来说它就是唯一</p>
</blockquote>
<p>锚定效应</p>
<blockquote>
<p>阮云：其实就是被带到沟里了</p>
</blockquote>
<p>光晕效应</p>
<blockquote>
<p>阮云：情人眼里出西施</p>
</blockquote>
<p>若其他投入固定，而不断增加一种可变投入的数量，则产出的增量最终必然递减。</p>
<p>确保分配给生成活动的下一单位投入有最高的边际产量，规模收益递增</p>
<p>完全竞争的四大条件：</p>
<ol>
<li>在完全竞争市场销售标准化产品</li>
<li>企业是价格接受者</li>
<li>自由进入和退出</li>
<li>企业和消费者掌握完全信息</li>
</ol>
<p>面对需求的定价弹性</p>
<p>只有当买家之间无法进行交易时，价格歧视才可行。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>经济学</tag>
      </tags>
  </entry>
  <entry>
    <title>《国史大纲》读书笔记</title>
    <url>/2019/11/27/%E3%80%8A%E5%9B%BD%E5%8F%B2%E5%A4%A7%E7%BA%B2%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-11-27-15748658640077.jpg"></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>国史大纲是钱穆先生大作，本是民国大学生的历史教科书。<br>所谓大纲者，不在完备，而是有提纲振领的作用。从中更是能体会到文化的精神，不是史料堆砌，不是无感情的上帝视角。</p>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>国家财赋偏向南方，向宋始</p>
<blockquote>
<p>阮云：明朝时，科举分南北榜，南远胜北，自是有其缘由。</p>
</blockquote>
<p>定州何明远资财巨万，家有绞机百张 – 太平广记</p>
<blockquote>
<p>阮云：所谓资本主义萌芽</p>
</blockquote>
<span id="more"></span>

<p>石晋尚能岁输契丹绢三十万匹</p>
<blockquote>
<p>阮云：可见工商业以及相当兴盛</p>
</blockquote>
<p>运河的开浚，其目的既专在通漕，对于北方原来水利，亦有损无益。</p>
<p>淮为河夺，七百七十余年。</p>
<blockquote>
<p>阮云：黄河决堤，夺淮河水道入海</p>
</blockquote>
<p>至元（忽必烈）时，江南技艺之人，乎曰“巧儿”，其价甚贵，北人得之，虑其逃遁，或以药哑其口，以火烙其足。</p>
<blockquote>
<p>阮云：故，庄子云，无用之用是为大用</p>
</blockquote>
<p>义者，本心所当为而不能自已，非有所为而为之者。</p>
<p>世族门第消灭，社会间日趋于平等，而散漫无组织，社会一切公共事业，均有主持领导之人，若读书人不管社会事务应科举，做官，谋身价富贵，则政治社会事业，势必日趋腐败。</p>
<blockquote>
<p>阮云：然而，还不是公务员，真香</p>
</blockquote>
<p>民穷财尽，为蕴乱之源。</p>
<blockquote>
<p>阮云：无恒产者无恒心。苟无恒心，放辟邪侈，无不为已。</p>
</blockquote>
<p>世运物力，则实清不如明，康熙五十年所谓盛世人丁者，尚不及明万历之半数。</p>
<blockquote>
<p>阮云：康乾盛世，一是人红利，二是番薯等高产粮食作物传入。</p>
</blockquote>
<p>用邪教的煽动起事，用流动的骚扰展开，这是安静散漫的农民所以能走上长期叛变的两条路子。</p>
<blockquote>
<p>阮云：农民自有其根性所在，目光很难超出一季作物的生长周期之外</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>随感</tag>
      </tags>
  </entry>
  <entry>
    <title>《在华五十年》读书笔记</title>
    <url>/2019/10/01/%E3%80%8A%E5%9C%A8%E5%8D%8E%E4%BA%94%E5%8D%81%E5%B9%B4%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-10-02-15699994564737.jpg"></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>之前对于传教士无甚好感，觉得他们总是将自己的信仰推销给别人，然而信仰的好坏暂且不论，总归是要自由的。</p>
<p>对司徒雷登的第一印象应该是来自《别了，司徒雷登》一文，毛主席对美帝一通抨击，连带着对他观感也不好了。</p>
<p>读了这本书才发现，一个较为真实的司徒雷登，了解他当时所处的社会现状，以及他说做的一些事情。</p>
<p>最为佩服的还是他对当时中国教育的帮助，在燕京大学上投注的心血，这些都足以为其生平作注了。</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>彼拉多见说也无济于事，反要生乱，就拿水在众人面前洗手，说，流这义人的血，罪不在我，你们承当吧。</p>
<blockquote>
<p>出自圣经</p>
</blockquote>
<p>中国人的思维方式不同，善于察言观色，认清局势并从中牟利</p>
<blockquote>
<p>认识可谓深刻</p>
</blockquote>
<blockquote>
<p>认识可谓深刻</p>
</blockquote>
<p>一群美国人为我国的教育奔走呼告，如此高义着实令人佩服</p>
<blockquote>
<p>拜服</p>
</blockquote>
<blockquote>
<p>拜服</p>
</blockquote>
<p>宗教信仰的表达，应当只是私人的事情</p>
<p>乱世的思想发展较好</p>
<blockquote>
<p>如，战国时期百家争鸣，应当是乱世大家生活不好，在思想上就会有所宣泄，不平则鸣嘛。而且乱世当权者对思想的管控也小，各种思想也得以发展。</p>
</blockquote>
<p>汪精卫：革命投机者</p>
<p>国共内战，与毛蒋两人的特质有很大关系</p>
<p>苏联非共产主义而是修正，这在司徒雷登早已明了</p>
<p>战争中的悲剧是中产阶级的生命和财产毁灭殆尽</p>
<p>没有自由，就算赢了全世界也毫无意义</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>随感</tag>
      </tags>
  </entry>
  <entry>
    <title>读《在细雨中呼喊》感悟</title>
    <url>/2019/09/22/%E3%80%8A%E5%9C%A8%E7%BB%86%E9%9B%A8%E4%B8%AD%E5%91%BC%E5%96%8A%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/15691588851421.jpg"></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>《在细雨中呼喊》是一本关于记忆的书，是余华发表于1991年的第一部长篇小说。</p>
<p>作者以第一人称讲述了一个家庭的欢乐和苦痛，孙光林、孙光明、孙光平兄弟三人的际遇，及他们所处的家庭和社会，常常让读者有种无力感，又带有些微的庆幸。</p>
<p>还是最深的体会就是，为人父母者永远不要将自己的懦弱施加到孩子身上。世间苦难重重，孩子本无意来此世上，还遭受无谓之伤害，实在无辜。</p>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>这事给我哥哥留下了深刻的印象，有一次他神情黯然地说：“当我们想成为城里人时，城里人却在想成为歌唱家。</p>
<blockquote>
<p>你拼命去追求的目标，到头来不过是别人一直想舍弃的</p>
</blockquote>
<p>孙光明在临死的前一天，还坐在门槛上向孙光平打听村里谁快要结婚了，他发誓这次要吃十颗水果糖。他说这话时鼻涕都流进了嘴巴。</p>
<blockquote>
<p>无常啊无常，世间事常是如此，不管是如此简单的快乐，还是鲜花着锦的富贵，都不能长久</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>随感</tag>
      </tags>
  </entry>
  <entry>
    <title>《大话设计模式》读书笔记</title>
    <url>/2016/06/21/%E3%80%8A%E5%A4%A7%E8%AF%9D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="面向对象基础"><a href="#面向对象基础" class="headerlink" title="面向对象基础"></a>面向对象基础</h2><ol>
<li>类是对对象的抽象，抽象类是对类的抽象，接口是对行为的抽象。</li>
<li>如果行为跨越不同类的对象，可使用接口；对于一些相似的类对象，用继承抽象类</li>
</ol>
<p>抽象类是自底向上抽象出来的，接口是自顶向下的设计，也就是接口是预设计，抽象类是后抽象。<br>所有设计模式都是为了维护的方便，如果你的代码确定不需复用，阅后即焚，则设计模式是多余的。</p>
<p>敏捷开发：通过重构改善既有代码的设计</p>
<p>单一职责原则：就一个类应该只有一个引起它改变的原因</p>
<p>开放-封闭原则：对于扩展开发，对于更改封闭</p>
<p>依赖倒转原则：高层模块不应该依赖底层模块，两个都应该依赖抽象；抽象不应该依赖细节，细节应该依赖抽象。</p>
<p>里式代换原则：依赖父类则可用子类代替，反之则不可（子类型必需能够替换父类型）</p>
<p>迪米特法则：如果两个类不必彼此相互相互通信，那么这两个类就不应该发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法，可以通过第三者转发这个调用。</p>
<span id="more"></span>

<h2 id="各种设计模式"><a href="#各种设计模式" class="headerlink" title="各种设计模式"></a>各种设计模式</h2><h3 id="简单工厂"><a href="#简单工厂" class="headerlink" title="简单工厂"></a>简单工厂</h3><p>声明抽象产品父类，使用工厂生产具体的产品子类（根据条件进行判断）</p>
<h3 id="策略模式"><a href="#策略模式" class="headerlink" title="策略模式"></a>策略模式</h3><p>context类聚合抽象策略基类，并且含有目标方法，使用时将具体的策略实例传人context的构造方法，然后调用context的目标方法</p>
<h3 id="策略与简单工厂结合"><a href="#策略与简单工厂结合" class="headerlink" title="策略与简单工厂结合"></a>策略与简单工厂结合</h3><p>不向context传入具体策略实例，而是传入类型参数，然后在构造函数中根据参数新建相应的策略实例</p>
<h3 id="装饰模式"><a href="#装饰模式" class="headerlink" title="装饰模式"></a>装饰模式</h3><p>装饰者和被装饰者继承同一基类，新建装饰者时将被装饰者作为参数传入，然后调用装饰者，关键是装饰者实现了被装饰者的功能。</p>
<h3 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h3><p>代理和使用代理者实现同一接口，使用代理者的所有操作都由代理执行（代理有使用代理者的引用）</p>
<h3 id="工厂方法"><a href="#工厂方法" class="headerlink" title="工厂方法"></a>工厂方法</h3><p>具体工厂都实现了工厂方法接口，每个工厂负责一类产品，由客户端（用户）选择实例化哪个工厂。</p>
<h3 id="原型模式"><a href="#原型模式" class="headerlink" title="原型模式"></a>原型模式</h3><p>新实例是旧实例的一个复制，并不调用构造方法。试用于实例化开销比较大的场合。</p>
<h3 id="模版方法"><a href="#模版方法" class="headerlink" title="模版方法"></a>模版方法</h3><p>抽象类定义了算法骨架，子类重写特定步骤。</p>
<h3 id="外观模式"><a href="#外观模式" class="headerlink" title="外观模式"></a>外观模式</h3><p>子系统中有多个模块，定义外观类，整合了子系统中的方法，从而简化调用，类似点菜时直接点一个套餐。</p>
<h3 id="建造者模式"><a href="#建造者模式" class="headerlink" title="建造者模式"></a>建造者模式</h3><p>抽象建造者定义建造各部分所需的方法，指挥者确定具体的建造步骤（引用抽象建造类），使用时传入具体建造类。</p>
<h3 id="观察者模式"><a href="#观察者模式" class="headerlink" title="观察者模式"></a>观察者模式</h3><p>通知者持有每个观察者的引用，当被观察的资源或对象的状态发生改变时，通知者就通过notify方法调用每个观察者的相关方法通知观察者。</p>
<h3 id="抽象工厂"><a href="#抽象工厂" class="headerlink" title="抽象工厂"></a>抽象工厂</h3><p>抽象工厂有多个工厂方法，用以新建一个产品序列的多个产品，若有多个系列则可新建多个工厂子类。</p>
<h3 id="状态模式"><a href="#状态模式" class="headerlink" title="状态模式"></a>状态模式</h3><p>Context同组合state抽象类来表示各种状态，每个状态类都有相同方法的不同实现，这样可以通过改变state对象来改变操作。</p>
<h3 id="适配器模式"><a href="#适配器模式" class="headerlink" title="适配器模式"></a>适配器模式</h3><p>适配器继承目标类，关联到被适配类，通过重新组合被适配类的方法来实现目标方法。</p>
<h3 id="备忘录模式"><a href="#备忘录模式" class="headerlink" title="备忘录模式"></a>备忘录模式</h3><p>发起者将自身状态传入备忘录的构造函数，并将备忘录对象由管理者持有；恢复时则从管理者处取出备忘录对象，调用备忘录的相关方法恢复数据。</p>
<h3 id="组合模式"><a href="#组合模式" class="headerlink" title="组合模式"></a>组合模式</h3><p>定义抽象类Composite，树叶和树枝都继承自Composite，树枝通过内部的list储存子节点，树叶和树枝（节点）实现相同的行为。<br>这样就可以将树叶和树枝都统一遍历。</p>
<h3 id="迭代器模式"><a href="#迭代器模式" class="headerlink" title="迭代器模式"></a>迭代器模式</h3><p>提供一种方法顺序访问一个聚合对象中的各个元素，而又不暴露该对象的内部表示。</p>
<h3 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h3><p>使构造函数私有，提供一个getinstance方法获得实例，在getinstance方法内部判断是否新建实例。</p>
<h3 id="桥接模式"><a href="#桥接模式" class="headerlink" title="桥接模式"></a>桥接模式</h3><p>桥的两侧都有很多地方，通过组合，实现多种多样的搭配。</p>
<h3 id="命令模式"><a href="#命令模式" class="headerlink" title="命令模式"></a>命令模式</h3><p>命令对象将命令执行者封装起来，并通过命令管理者来处理命令队列。</p>
<h3 id="职责链模式"><a href="#职责链模式" class="headerlink" title="职责链模式"></a>职责链模式</h3><p>各个处理者继承自同一基类，并且他们处于同一条链上，设定某个条件，满足条件的处理者处理，否则转下一级。</p>
<h3 id="中介者模式"><a href="#中介者模式" class="headerlink" title="中介者模式"></a>中介者模式</h3><p>用一个中介对象来封装一系列的对象交互，中介者使各对象不需要显式地相互引用。</p>
<h3 id="享元模式-fly-weight"><a href="#享元模式-fly-weight" class="headerlink" title="享元模式(fly weight)"></a>享元模式(fly weight)</h3><p>享元工厂flyWeightFactory使用Hashtable记录已生成的各类实例，以降低实例化的损耗。</p>
<h3 id="解释器模式"><a href="#解释器模式" class="headerlink" title="解释器模式"></a>解释器模式</h3><p>有终结符表达式类、非终结符表达式类，两者都继承自抽象表达式类，并且都实现了一个解释方法。</p>
<h3 id="访问者模式"><a href="#访问者模式" class="headerlink" title="访问者模式"></a>访问者模式</h3><p>访问者种类固定，业务多变，定义业务类，将访问者作为对象传入。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>设计模式</tag>
      </tags>
  </entry>
  <entry>
    <title>房龙:《宽容》摘录</title>
    <url>/2020/03/22/%E3%80%8A%E5%AE%BD%E5%AE%B9%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>补录，笔记太过久远，原文已然忘记。</em></p>
<p>泰勒斯: 世间万物来源于水</p>
<p>苏格拉底: 把大量的时间用在空洞的欢乐和虚无的胜利上使年华虚度, 这样的生活毫无意义; 为了满足自己小心的虚荣心和野心而肆意挥霍上帝赐予的一切.</p>
<p>在基督徒眼里, 这个世界仅是通往天堂的前厅</p>
<p>摩尼教: 3世纪前叶创始人摩尼出生</p>
<p>现代的不宽容可以分为出于懒惰的不宽容, 出于无知的, 出于自私自利的.</p>
<p>一些人靠某种以及建立的崇拜谋生, 另一些人却要把他们从一个寺庙引到另一个寺庙. 他们之间的战争一直是公开的. </p>
<p>今天的异教徒, 明天就是一切持不同意见的人的劲敌</p>
<p>世界上本来只有一个暴君, 激进派却带来了两个</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《少有人走的路》读书笔记</title>
    <url>/2020/03/29/%E3%80%8A%E5%B0%91%E6%9C%89%E4%BA%BA%E8%B5%B0%E7%9A%84%E8%B7%AF%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em></p>
<p>放弃人生的某些东西，一定会给心灵带来痛苦。</p>
<p>从生理上决定了人是目光短浅的，因为离你最近的东西看起来总是比较大。</p>
<p>完全接受痛苦，在某种意义上痛苦就不再存在。</p>
<span id="more"></span>

<p>在全知全能的状态下做决定，远比在一知半解的状态下，要更痛苦。</p>
<p>为了放弃，首先必须拥有某种事物。你不能放弃从来没有的事物。</p>
<p>自律的四种原则：推迟满足感，承担责任，尊重事实，保持平衡。</p>
<p>坠入情网，意味着自我界限的某一部分突然崩溃，使我们的“自我”与他人合而为一。</p>
<p>坠入情网是人类基因对于人类理性的征服。</p>
<p>真正爱的本质之一，就是希望对方拥有独立自主的人格。</p>
<p>因为缺乏安全感，不能推迟满足感</p>
<blockquote>
<p>阮云：所以过度享乐，放纵，寻找作弊器</p>
</blockquote>
<p>缺乏耐心，拖延症其实是同一样东西</p>
<p>问题没有消失，它们仍继续存在，它们是妨碍心灵成长的永远的障碍。</p>
<p>坚守过时的观念，对现实漠然处之，称之为“移情”。</p>
<p>生活在封闭的系统里 —— 就像是单间牢房，我们反复呼吸自己释放的恶臭空气。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《幸福的情绪》读书笔记</title>
    <url>/2020/05/05/%E3%80%8A%E5%B9%B8%E7%A6%8F%E7%9A%84%E6%83%85%E7%BB%AA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记, 也是所有旧读书笔记的最后一篇</em></p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>本书主要讲述的是，我们必须去理解我们的情绪，情绪只是外显的东西，我们需要了解为何在这个时刻会产生这种情绪，它表明了什么。</p>
<p>借用书中的一句话：<br><strong>情绪或多或少地行使着相同的目的，那就是从一个艰难的世界中逃离。</strong></p>
<span id="more"></span>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>被冒犯的人通过扭转局面，将冒犯者置于失利的位置，从而保全自己的面子。</p>
<blockquote>
<p>阮云：所以弱者常常发怒。</p>
</blockquote>
<p>愤怒与否首先取决于它是否符合个人的长期利益。</p>
<p>愤怒如果无人知晓，那么它也失去了它存在的意义。</p>
<p>愤怒是对外的，恐惧是对内的。</p>
<blockquote>
<p>阮云：愤怒向外传递信息，恐惧让自己提高警惕</p>
</blockquote>
<p>恐惧或多或少地与某些自动的，即本能的大脑反应有关。</p>
<p>情绪更倾向于有明确或者具体的对象，而心境更倾向于不确定的对象。</p>
<p>大多数人需要学习的窍门是，能够快速地将那些快乐的时刻扩散到我们对整个世界的感觉中去。</p>
<p>性冲动（libido）利比多</p>
<blockquote>
<p>阮云：人之所以诉说就是渴望认同，故而倾听是极为重要的。</p>
</blockquote>
<p>阿里斯托芬：旧时有人四手四腿两个脑袋，宙斯因为人类的傲慢而将他们劈成两半，其中一半在地球上流浪，寻找“他的另一半”</p>
<p>爱共有的结构，那就是一个人的自我与另一个人的自我缠绕融合在一起。</p>
<p>同情带有明显的行动倾向。</p>
<p>当悲伤欢笑幸福放在一起时，它们才是最有意义的。</p>
<p>悲伤涉及的是自我的缺失。</p>
<p>当我们感到羞愧的时候，我们所意识到的自我以及我们真实存在常常是最为痛苦的。</p>
<p>羞愧、内疚、窘迫 – 自我评价型的情绪</p>
<p>窘迫涉及复杂的自我认知，这个自我是身处众人中的自我，它会受到别人期待和判断的影响，而这种影响被自我内化。</p>
<p>有责任感的人才会懊悔。</p>
<!--加缪：《局外人》《鼠疫》《堕落》-->
<p>骄傲和羞愧 – 自我评价型，带有部落特征</p>
<p>羡慕会使人有持续不断的挫败感，使人低估自己拥有的东西，挫败感超出了能承受的范围时，羡慕 -&gt; 怨恨</p>
<p>最直接有效的情绪理解方式应该是将注意力集中在我们的个人体验上。</p>
<p>情绪或多或少地行使着相同的目的，那就是从一个艰难的世界中逃离。</p>
<p>一个人爱的根本就不是爱人本身，而是一种投射。或是一种满足愿望的幻影，这一投射或幻影可能和真实的血肉之躯根本就没有什么关系。</p>
<p>为什么我要这么做，我从中能够获得什么</p>
<blockquote>
<p>阮云：理解你的情绪</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《怪诞行为学2》读书笔记</title>
    <url>/2020/04/10/%E3%80%8A%E6%80%AA%E8%AF%9E%E8%A1%8C%E4%B8%BA%E5%AD%A62%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em><br><em>本书解析一系列不合常理的行为, 分析背后的原因. 难度较小, 属于入门读物</em></p>
<p>发放高额奖金对简单机械操作类工作可以提高成绩, 如果需要动脑, 则适得其反.</p>
<!--薪水与工作成果哪个更重要?-->

<!--"反寄生", 动物不愿不劳而食-->

<p>如果你找的人喜欢某一事物，你把他们放在有意义的工作条件下，他们从这个活动中得到的快乐会成为影响他们努力程度的主要动力。</p>
<p>相反，如果你把有同样热情和欲望的人放到无意义的工作条件下，就非常容易扼杀这项活动在他们心中引发的快乐。</p>
<p>劳动异化:</p>
<ol>
<li>劳动者同自己的劳动产品相异化。</li>
<li>劳动者同自己的劳动活动相异化。</li>
<li>人同自己的类本质相异化，即人同自由自觉的活动及其创造的对象世界相异化。</li>
<li>人同人相异化。因为当人同自己的劳动产品、自己的劳动活动以及自己的类本质相对立的时候，也必然同他人相对立。</li>
</ol>
<span id="more"></span>

<p>“孩子是自己的好”效应: 如果不是我发明的, 那就没什么价值.</p>
<p>惩罚冲动: 这一切说明实行报复—即使需要付出代价也要报复，这种欲望是有生物学基础的，而且这种行为事实上能获得快感（或者起码引发类似快感的反应）</p>
<p>快感适应: 这种情绪上的逐渐稳固现象—原有的正面感觉淡化，负面感觉也减弱—我们把这一过程称做快感适应。</p>
<p>中断厌恶的体验感是有害的, 中断愉悦感的体验过程有益.</p>
<p>真正的进步, 与真正的愉悦一样, 来自冒险和对不同事物的尝试.</p>
<p>我们的适应过程, 一定程度上受周围环境影响.</p>
<p>我们一旦感情用事, 救会做出短期决定, 而它却会改变我们很多的长期决定.</p>
<p>禀赋效应: 是指当个人一旦拥有某项物品，那么他对该物品价值的评价要比未拥有之前大大增加。</p>
<p>损失厌恶是一个简单的概念，人们失去自认为是属于自己的东西时会产生痛苦—比如钱—而这一痛苦要大于得到同样数量的钱所产生的愉悦感。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《情商》摘录</title>
    <url>/2020/05/05/%E3%80%8A%E6%83%85%E5%95%86%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em></p>
<p>对抑郁的忧虑会使我们的抑郁更加严重</p>
<p>忧虑是一项认知任务，个体如果把心理资源用于忧虑，就分散了用于处理其他信息的心理资源。</p>
<span id="more"></span>
<p>社交 – 情绪同步性</p>
<p>就事论事 – 维护关系</p>
<blockquote>
<p>阮云：单纯地发泄情绪对于关系是没有多大帮助的，你要表达出你的诉求。</p>
</blockquote>
<p>暴力行为的入侵性记忆</p>
<p>创伤有应激障碍引发的大脑变化是可以消除的，可从最直接的情绪印记当中复原，治愈的途径就是再学习。</p>
<p>创伤复原：获得安全感，记住创伤细节并哀悼由此造成的损失，最有重新恢复正常的生活。</p>
<p>行为疗法与药物一样有效。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《意志力：受益一生的潜能开发工具》读书笔记</title>
    <url>/2020/04/24/%E3%80%8A%E6%84%8F%E5%BF%97%E5%8A%9B%EF%BC%9A%E5%8F%97%E7%9B%8A%E4%B8%80%E7%94%9F%E7%9A%84%E6%BD%9C%E8%83%BD%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em><br><em>老拖延症又企图从这本书中获得救赎之道，然而这笔记也拖延了许久。</em></p>
<p>意志力训练的关键：专注于目标，合乎道德。</p>
<p>意志力需要渐渐积累的</p>
<p>增强意志力的心态：兴趣，充沛的精力，顺其自然，理解（对事情的理性分析与接受），正直，自尊。</p>
<p>意志力走偏的表现与诊治：</p>
<ol>
<li>缺乏激情：规划日程，努力实现一个目标。</li>
<li>迟疑不决：积极乐观地看待问题，努力看到事情积极面</li>
</ol>
<p>教育的关键是：让我们的内心成为我们忠诚的朋友，而不是对立的敌人</p>
<span id="more"></span>

<p>不要一味仿效他人，那样只会讲你变成他人的影子，难以有所突破。要想有自己的看法，他就要有意识地计费新想法，开拓新思维，从而找到自己的发展之路。</p>
<p>不要懈怠目前从事的任何工作。</p>
<p>如果头脑里同时在考虑许多事情，就不要在此时下决定，因为你不可能同时既钓鱼又打猎。</p>
<p>意志力训练方法</p>
<ol>
<li>视觉训练：a. 以平常的步速走入房间，在房间里转一圈，然后走出房间，并记录下你刚才看到的全部物品。b. 目视前方，凝视其中一个离自己较近的物体，默数六十下以后，闭上眼睛，努力回想物体的轮廓和具体形象。c. 无论遇到谁，都要目光沉稳，态度坦然地看着对方，并让对方的一言一行全部在你的目光之中。</li>
<li>听觉训练：用心倾听，并记录下自己听到的所有声音，分辨其来源强度。</li>
<li>每天临睡前，回想一下白天见到的任何人和遇到的任何事，直到你能回忆起更多的人；你想去某个地方的时候看一下地图，闭上眼睛回忆地图上的标识。</li>
</ol>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《意志力：关于专注、自控与效率的心理学》读书笔记</title>
    <url>/2020/04/23/%E3%80%8A%E6%84%8F%E5%BF%97%E5%8A%9B%EF%BC%9A%E5%85%B3%E4%BA%8E%E4%B8%93%E6%B3%A8%E3%80%81%E8%87%AA%E6%8E%A7%E4%B8%8E%E6%95%88%E7%8E%87%E7%9A%84%E5%BF%83%E7%90%86%E5%AD%A6%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em><br><em>老拖延症企图从这本书中获得救赎之道，然而这笔记也拖延了许久。总体来说理论是很有道理的。</em></p>
<p>人们醒着的时候，把大约1&#x2F;4的时间用来抵制欲望，每天至少4个小时。首先是食欲，其次睡欲，休闲欲，性欲，交往欲。</p>
<p>平均而言，用意志力抵制欲望，10次当中只有5次是成果的。</p>
<blockquote>
<p>阮云：严重高估，我大概有一次成功</p>
</blockquote>
<p>邓巴最后下结论，大脑进化得越来越大，并不是为了应付物理环境，而是为了应付社会环境。</p>
<p>意志力像肌肉一样，过度使用会疲劳，长期锻炼会增强。</p>
<p>自我损耗：人们对自己思维感受和行为调节能力减弱的过程。</p>
<span id="more"></span>

<p>压力真正做的是损耗意志力，而这会减弱情绪控制能力。</p>
<blockquote>
<p>阮云：真是感同身受，以前有段时间压力大，常常发无明业火。</p>
</blockquote>
<p>你的意志力是有限的，使用就会消耗；你从同一账户提取意志力用于各种不同任务。</p>
<p>为了保持自制力，吃转化为葡萄糖慢的食物（蔬果鱼肉蛋奶）</p>
<blockquote>
<p>阮云：所以穷人普遍缺乏自制力，部分原因是高碳水饮食</p>
</blockquote>
<p>互相冲突目标造成：1. 愁得多；2. 做得少； 3. 身心健康变差</p>
<p>为不太重要的熟人做决定消耗的意志力远少于为自己做决定。</p>
<p>意志力下降，偏爱做风险小的选择，比如说找借口避免、推迟决策。</p>
<p>折中是类别特别高级，特别困难的决策，折中能力是意志力耗尽之后第一个衰退的能力</p>
<blockquote>
<p>阮云：所以推销员让顾客疲于选择也是一种策略</p>
</blockquote>
<p>练习情绪控制不会增强你的意志力；意志力的“力量”不同于意志力的“耐力”</p>
<p>提升意志力的关键，集中精力改变一个习惯。</p>
<p>许可效应：做了好事就表现得好像有了做坏事的权利</p>
<blockquote>
<p>阮云：做了运动就喝杯奶茶奖励自己</p>
</blockquote>
<p>情绪温差：在理性，冷静的“冷温”状态，体会不到充满激情和欲望的“高温”状态有何表现。</p>
<blockquote>
<p>阮云：人的感觉中的时间并不是均匀分布的，离你近的时间会变短，最后期限是做加速运动向人而来。</p>
</blockquote>
<p>一旦用意志力把刮胡子变为习惯，刮胡子就变成了相对自动的心智过程，再也不用什么意志力。</p>
<p>棉花糖实验：一直盯着棉花糖的孩子迅速耗尽了意志力，分散注意力的孩子成功坚持下来。</p>
<p>宗教有利于增强自我控制的两个要素：意志力和行为监控。</p>
<p>建议无神论者寻找自己的一套神圣价值观。</p>
<p>零容忍是一条明线：完全戒除，任何时候都没例外，一旦遵守一条明线规则，你“现在自我”会相信“未来自我”也会遵守它。</p>
<p>高自尊好处：</p>
<ol>
<li>提高主动性，很可能因为它增强自信</li>
<li>它让人感觉良好</li>
</ol>
<p>惩罚有3个基本方向：严厉性，及时性，一致性（后两者更为重要）</p>
<p>费伯入眠法：忽略婴儿哭泣 – 安慰 – 走开 – 重复到入睡</p>
<p>进化喜欢那种能在饥荒中活下来的人，所以身体一旦有吃不饱的经历就会努力保存所有脂肪。</p>
<blockquote>
<p>阮云：所以节食减肥容易反弹</p>
</blockquote>
<p>反条件进食（去它的效应）：超过限量后大吃大喝</p>
<blockquote>
<p>阮云：破罐子破摔</p>
</blockquote>
<p>实施意向（implement intention）: 把行为自动化。实施意向要以“如果”…“那么”…形式出现， 如果x发生了，我一定会做y，利用这个技术把行为自动化，减少消费的努力</p>
<p>告诉自己“稍后再吃”，在心理上和现在就吃一样</p>
<p>损耗意志力的是斗争而不是结果</p>
<p>帕金森定律：有多少时间，工作就能做多久。</p>
<p>计划谬误：高估能力，低估所需时间。解决方法：强迫自己想过去的做法。</p>
<p>想做不好的事时，请尽量往后拖延。</p>
<p>如果你只用意志力拒绝东西，那么它就成了残忍讨厌的防守工具。但是当你用意志力获得东西，你就能从最枯燥的任务中体会到乐趣。</p>
<blockquote>
<p>阮云：所以做苦行僧不如，得到应得的奖励。</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《我们台湾这些年》读书笔记</title>
    <url>/2019/12/01/%E3%80%8A%E6%88%91%E4%BB%AC%E5%8F%B0%E6%B9%BE%E8%BF%99%E4%BA%9B%E5%B9%B4%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-12-01-15752068894466.jpg"></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>历史到底是什么样子的？很难确定。</p>
<p>我们所经历的生活，在若干年后就会是历史，但是我现在仍看不清现实。想必后来人看现在也是一样的，仿佛笼罩在烟尘之中。</p>
<p>有的台湾人对大陆建国之后的历史有着错误的认识，视我们为洪水猛兽，但反过来，我们对他们的了解又有多少呢？</p>
<p>这本书就是以台湾历史亲历者的角度，诉说进几十年的历史，普通人眼中台湾的过往。</p>
<span id="more"></span>

<h2 id="大事件"><a href="#大事件" class="headerlink" title="大事件"></a>大事件</h2><ul>
<li>1978年<ul>
<li>蒋经国继任</li>
<li>“十大建设”</li>
</ul>
</li>
<li>1980年<ul>
<li>美丽岛大审（陈水扁、谢长廷作为律师，真是讽刺啊）</li>
<li>加速开放</li>
<li>林义雄命案</li>
</ul>
</li>
<li>1981年<ul>
<li>陈文成命案</li>
<li>台湾禁金庸书（疑似因为笑傲江湖）</li>
<li>外省老兵的不幸（唏嘘）</li>
</ul>
</li>
<li>1986年<ul>
<li>《庭院深深》 琼瑶</li>
</ul>
</li>
<li>1987年<ul>
<li>解严，结束38年的戒严令</li>
</ul>
</li>
<li>1988年<ul>
<li>《爱拼才会赢》叶启文（童年记忆之一）</li>
<li>汉城奥运会：卡尔·刘易斯与本·约翰逊，百米飞人大战</li>
</ul>
</li>
</ul>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>随感</tag>
      </tags>
  </entry>
  <entry>
    <title>《找回逝去的自我--大脑心灵和往事的记忆》读书笔记</title>
    <url>/2019/10/06/%E3%80%8A%E6%89%BE%E5%9B%9E%E9%80%9D%E5%8E%BB%E7%9A%84%E8%87%AA%E6%88%91%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><a href="https://book.douban.com/subject/5406389/"> <img data-src="https://image.ponder.work/mweb/2019-10-06-15703330203326.jpg"> </a></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>作者: <a href="https://book.douban.com/search/%E4%B8%B9%E5%B0%BC%E5%B0%94%C2%B7%E5%A4%8F%E5%85%8B%E7%89%B9">丹尼尔·夏克特</a> &#x2F; <a href="https://book.douban.com/search/Daniel%20L.%20Schacter">Daniel L. Schacter</a></p>
<p>顾名思义，这是一本关于记忆的书，讲述了人脑记忆的机制。我们是如何储存，并提前记忆的。</p>
<p>阅读这本书，对理解记忆，寻找改善记忆的方式有很大帮助。</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>对往事的利用依赖于：</p>
<ol>
<li>语义记忆: 包括概念的及事实的知识</li>
<li>程序记忆: 是我们学习技能和形成习惯的基础</li>
</ol>
<p>记忆活动的两种模式：</p>
<ol>
<li>场域记忆：以事件发生的原始方式重新加以体验(第一人称视角，注意感情）</li>
<li>观察者记忆: 必然是记忆的原始事件的变化形式(第三人称视角，注意客观环境回忆）</li>
</ol>
<p>一个记忆的感情强度，部分地决定于你的回忆方式</p>
<p>一件事，我们仅仅是知道它发生过，还是记住了，取决于它发生时是如何加以注意的。</p>
<p>对某事记忆印象，一方面取决于该事在过去的发生，另一方面也取决于目前回忆时所发生的情况</p>
<p>短时时记忆，工作记忆系统。</p>
<blockquote>
<p>类似电脑RAM</p>
</blockquote>
<p>“深度加工”效应,要想建立长久的记忆，那就必须对所接受的信息进行更彻底或更深入的编码，即以一种有意义的方式将这一信息与已经存在于记忆之中的知识相联结。</p>
<blockquote>
<p>将信息赋予意义，能改善记忆</p>
</blockquote>
<p>记忆是大脑欲赋予环境以秩序的一种努力</p>
<p>精细编码为回忆提供看更多的线索</p>
<p>只有未消退的记忆在碰到适当的回忆线索时，才能被记起</p>
<p>提取线索具有的特征，会影响我们的回忆结果</p>
<p>新的编码和储存会干扰先前记忆的回忆能力</p>
<p>记忆歪曲在一点程度上是由编码过程引起的</p>
<p>提取记忆唤醒了沉睡的记忆影像，还有当时的主观体验。</p>
<p>海马系统，与近期经验的外显记忆相关</p>
<p>颞叶中央区系统损伤引起失忆症</p>
<p>注意力的集中是形成新的的外显记忆的必要条件</p>
<p>内隐记忆与外显记忆</p>
<p>启动效应：与事件相关的线索，有助于唤醒对该事件的记忆</p>
<p>启动效应的产生不依赖于有意识的记忆</p>
<p>启动效应依赖于某一以知觉为基础的记忆系统</p>
<p>闪光灯记忆（像拍照一样的记忆），比日常记忆保持得更好</p>
<p>某一事件发生之后，随时间的流逝，人们倾向于遗忘或者混淆该记忆的来源</p>
<blockquote>
<p>换个角度理解，已于遗忘的记忆一般都是单一来源的。</p>
</blockquote>
<p>回闪记忆：不由自主地产生、并具有强迫性质的种种回忆。</p>
<p>能引起情绪变动的材料记忆得更牢</p>
<p>情绪一致性提取：当你感到悲伤时，你似乎很容易产生各种消极的想法并记起痛苦的经历来。</p>
<p>杏仁核 – 情绪调节的关键结构</p>
<p>情绪之所以有助于记忆，是因为它引起了某些与应激性有关的激素释放</p>
<p>心因性失忆（因心理创伤而引起的暂时性记忆丧失） – 大脑损伤，情绪创伤的联合反应</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>记忆</tag>
      </tags>
  </entry>
  <entry>
    <title>《浮生六记》笔记</title>
    <url>/2019/11/02/%E3%80%8A%E6%B5%AE%E7%94%9F%E5%85%AD%E8%AE%B0%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-12-07-15756871927995.jpg?300x"></p>
<p>浮生六记算是沈复的自传吧，其人才情虽好，德行却是没什么称道的地方。当然他前半生神仙眷侣般的生活，也不知惹多少人羡慕，红袖添香夜读书大概就是如此吧。</p>
<p>名为六记，只余其四，也是人生一大憾事</p>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><h3 id="卷一-闺房记乐"><a href="#卷一-闺房记乐" class="headerlink" title="卷一 闺房记乐"></a>卷一 闺房记乐</h3><p>其形削肩长项，瘦不露骨，眉弯目秀，顾盼神飞，唯两齿微露，似非佳相。</p>
<blockquote>
<p>阮云：一会儿就该说真香了</p>
</blockquote>
<p>余镌“愿生生世世为夫妇”图章二方，余执朱文，芸执白文，以为往来书信之用。</p>
<blockquote>
<p>阮云：此生能得一良侣已是奢望，又怎么敢求生生世世。</p>
</blockquote>
<p>山果收获，必借猴力，果然。</p>
<p>余调其言，如蟋蟀之用纤草，渐能发议。其每日饭必用茶泡，喜食芥卤乳腐，吴俗呼为臭乳腐，又喜食虾卤瓜。</p>
<blockquote>
<p>阮云：喜欢饭后喝茶，腌制食品，早夭有因</p>
</blockquote>
<p>情之所钟，虽丑不嫌。</p>
<p>其癖好与余同，且能察眼意，懂眉语，一举一动，示之以色，无不头头是道。</p>
<p>于是相挽登舟，返棹至万年桥下，阳乌犹未落山。舟窗尽落，清风徐来，绒扇罗衫，剖瓜解暑。</p>
<span id="more"></span>

<h3 id="卷二-闲情记趣"><a href="#卷二-闲情记趣" class="headerlink" title="卷二 闲情记趣"></a>卷二 闲情记趣</h3><p>贪此生涯，卵为蚯蚓所哈（吴俗称阳曰卵），肿不能便，捉鸭开口哈之，婢妪偶释手，鸭颠其颈作吞噬状，惊而大哭，传为语柄。此皆幼时闲情也。</p>
<h3 id="卷三-坎坷记愁"><a href="#卷三-坎坷记愁" class="headerlink" title="卷三 坎坷记愁"></a>卷三 坎坷记愁</h3><p>人生坎坷何为乎来哉？往往皆自作孽耳，余则非也，多情重诺，爽直不羁，转因之为累。</p>
<p>况锦衣玉食者，未必能安于荆钗布裙也，与其后悔，莫若无成。</p>
<p>儿女之事粗能了了，但分离至此，令人终觉惨伤耳。</p>
<blockquote>
<p>阮云：为人父母者，如此大大不该。虽未抛妻，也算弃子，而后其子早夭。然为钱财所困，也可理解。</p>
</blockquote>
<p>总因君太多情，妾生薄命耳</p>
<blockquote>
<p>阮云：爱别离</p>
</blockquote>
<h3 id="卷四-浪游记快"><a href="#卷四-浪游记快" class="headerlink" title="卷四 浪游记快"></a>卷四 浪游记快</h3><p>茫茫沧海，不知此生再遇知己如鸿干者否？</p>
<p>吾乡之虎丘山，终日目所见者妖童艳妓，耳所听者弦索笙歌，鼻所闻者佳肴美酒，安得身如枯木、心如死灰哉？”</p>
<blockquote>
<p>阮云：当今之世,声色犬马几倍于其时，无怪乎人心散乱</p>
</blockquote>
<p>及终席，有卧吃鸦片烟者</p>
<blockquote>
<p>阮云：乾隆时期,虽然称为盛世,但衰弱的根子己经埋下了</p>
</blockquote>
<p>剪烛絮谈，始悉翠籍湖南，喜亦豫产，本姓欧阳，父亡母醮，为恶叔所卖</p>
<blockquote>
<p>阮云：流落烟花者,必有不幸之过往</p>
</blockquote>
<p>秀峰今翠明红，俗谓之跳槽，甚至一招两妓；</p>
<blockquote>
<p>阮云：跳槽甚是快意</p>
</blockquote>
<p>余四月在彼处，共费百余金，得尝荔枝鲜果，亦生平快事。</p>
<blockquote>
<p>阮云：回顾前文之困窘 一饮一啄自有定数</p>
</blockquote>
<h2 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h2><p><a href="https://www.duokan.com/book/341">多看免费版本</a></p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《燃灯者》摘录</title>
    <url>/2020/03/21/%E3%80%8A%E7%87%83%E7%81%AF%E8%80%85%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>补录，笔记太过久远，原文已然忘记。《维摩诘经》有云，无尽灯者，譬如一灯燃百千灯，冥者皆明，明终不尽。所谓燃灯者，大抵如此</em></p>
<p>文革十年甚于崖山者百倍</p>
<p>视其平居无以异于俗人，临大节而不可夺，此不俗人也</p>
<p>人民，人民，有多少罪恶借你的名义施行</p>
<p>殷鉴不远，多行不义必自毙</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《潜规则》读书笔记</title>
    <url>/2019/12/06/%E3%80%8A%E6%BD%9C%E8%A7%84%E5%88%99%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-12-07-15756868332950.jpg?300x"></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>潜规则应该是对三观冲击最大的书之一，也是作者在读遍历史的总结。对于政权的更迭，剖析其原因，以及背后的潜规则。</p>
<p>此书可以和<a href="https://book.douban.com/subject/3640150/">《血酬定律》</a>搭配阅读，效果更佳。</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>一个变质的政府，一个剥削性越来越强，服务型越来越弱的政府，自然也需要变质的官员，需要他们泯灭良心心狠手辣，否则就要请你走人。</p>
<blockquote>
<p>阮云：或者是说变质的官员构成了这样的政府，不容于这样环境的官员只能走人。</p>
</blockquote>
<p>袁宏道言官：如小儿看见了蜡糖人，啼哭不已非要吃，又唯恐唾只不尽，做官的滋味就是这样。</p>
<blockquote>
<p>阮云：形象形象，官迷的形状跃然纸上，现在这种人照样多得是，只是一些出现在职场里了。</p>
</blockquote>
<p>现代民谣：村骗乡，乡骗县，一级一级往上骗，一直骗到国务院。</p>
<p>新官堕落定律</p>
<blockquote>
<p>阮云：如同大学生进入社会。</p>
</blockquote>
<p>晏子转型：明规则向潜规则转变。</p>
<p>征税的压力越大，反叛的规模越大，帝国新增的暴力敌不过新生的反叛暴力。</p>
<blockquote>
<p>阮云：现代政府稳定的一大原因，低成本高效的征税</p>
</blockquote>
<p>中国地主历代的田租常规是产量的50%</p>
<blockquote>
<p>阮云：读史常使人心中不忍</p>
</blockquote>
<p>古时人相食：一个人单身走路经常失踪，被人像偷鸡摸狗一样悄悄杀了吃掉。</p>
<blockquote>
<p>阮云：所谓偷人</p>
</blockquote>
<p>当一个社会，当了强盗土匪的生存的可能性更大时，它的灭亡就不可避免了。</p>
<blockquote>
<p>阮云：类似癌症</p>
</blockquote>
<p>田地负担越重，苛捐杂税越多，田地就越不值钱。</p>
<blockquote>
<p>阮云：然后失地农民越多，土地兼并越严重，这时闯王就越有可能出现</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>历史</tag>
      </tags>
  </entry>
  <entry>
    <title>《狂热分子》笔记</title>
    <url>/2016/08/28/%E3%80%8A%E7%8B%82%E7%83%AD%E5%88%86%E5%AD%90%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>作者，埃里克·霍弗。本书主要讲述处于群众运动中的个体的心理状态。作者亲身参与过群众运动，发现这些运动的参与者主要是现实生活的失意者。他们抛弃自己本已无可救药的生活，投身于“神圣的”群众运动，化身为伟大集体的一份子，号召自我牺牲以获救赎。阅读本书或许会对中国的革命年代多一些不一样的理解。</p>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>愈做不好一般事情是人愈的胆大妄为。</p>
<p>希望并不是生命可以寄托的东西。</p>
<blockquote>
<p>阮云：除了精进，没有什么可以寄托生命。</p>
</blockquote>
<p>当你做不好分内的事时，别人会耻笑你，但当你帮助别人时，没人会耻笑你。</p>
<p>有成就感的人会把世界当成一个友好的世界，失意者则乐于看到世界急遽改变。</p>
<p>任何教义如果想要成为一种力量的来源，它就必须宣扬自己是打开未来之书（唯一）的钥匙。</p>
<span id="more"></span>


<p>无私者的虚荣心是无边界的。</p>
<p>一个群体的性格和命运，往往是由其最低等的成员决定。</p>
<p>悲愤会在它几乎得到补偿的时候最为蚀骨。</p>
<p>看它的组织能不能迅速将所有失意者集合起来。</p>
<p>君主专制政体，它们最危险的时刻就是开始改革的时刻，也就是开始流露自由倾向的时刻。</p>
<p>对于不是我们真正想要的东西，得到再多也不会满足。</p>
<p>得为三餐糊口的人不会有烦闷感。</p>
<blockquote>
<p>阮云：空闲时间多的人才会无聊</p>
</blockquote>
<p>群众运动的策略是要把一种病传染给人，然后又把自己说成是救命的良方。</p>
<p>逃避自我让人易于自我牺牲。</p>
<p>通过认同，个人不再是他自己，而成了某种永恒物的一部分。</p>
<p>没有对美好的未来抱深信不疑的信仰，把“现在”的贬抑就不够彻底，就没有办法让人投身到那“伟大而神圣”的事业当中去。</p>
<blockquote>
<p>阮云：现在的保守派以前必定是激进派，地位不同而已。</p>
</blockquote>
<p>渴望而非拥有，才是人们赴汤蹈火在所不辞的动力</p>
<p>教义不是让人去理解的，而是让人是去信仰的。</p>
<blockquote>
<p>阮云：因此，去证明马克思主义的合理与否不是很可笑吗。</p>
</blockquote>
<p>宗教狂热者的对立面不是狂热的无神论者，而是温和的愤世嫉俗者。</p>
<p>在所有团结的催化剂中，最容易应用的一项是仇恨。</p>
<blockquote>
<p>阮云：爱同一个对象时大家是情敌，恨同一个对象时大家是战友。因为吃的时候总希望独食，付钱是时候最好平分。自有世界以来从未有过慈悲的宗教或革命党。</p>
</blockquote>
<p>匆匆忙忙的生活更容易制造齐一性。</p>
<p>失意者乐于以独立性来交换免于做决定的自由，</p>
<blockquote>
<p>阮云：因为决定意味着责任。</p>
</blockquote>
<p>耶稣并不是基督徒，一如马克思不是马克思主义者。</p>
<blockquote>
<p>阮云：言辞者如马克思，只提供了一种构思；狂热者作为文章的载体——草稿，被行动者利用而实现这种构思。</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《社会契约论》读书笔记</title>
    <url>/2015/09/29/%E3%80%8A%E7%A4%BE%E4%BC%9A%E5%A5%91%E7%BA%A6%E8%AE%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><ul>
<li>作者：卢梭</li>
<li>主要谈论国家最初是凭借什么组织起来的，以及社会公约与公民自由之间的关系。</li>
</ul>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><ul>
<li><p>人是生而自由的，却无往不在枷锁之中。有些人自以为是一切的主人，但他们其实只是一切的奴隶。<br>  博主云：所谓心为形役</p>
</li>
<li><p>一开始没有人愿意做奴隶的，实力还是使有些人成为了第一批奴隶，这些人之所以永远当奴隶是因为他们自己的怯懦。</p>
</li>
<li><p>最强的人除非能把自己的实力转换为权利，把服从转化为义务，否则的话，最强的人也不可能永远强下去，更不会永远做主人。</p>
</li>
<li><p>每个人在这个社会公约遭到破坏时立刻会恢复他原来的权利，在丧失约定自由时又重新获得了他为了约定的自由而放弃的自己的天然的自由。</p>
</li>
<li><p>严格定义社会公约：我们在共同体中接纳每一个成员，使之成为全体不可分割的一部分，在公意的最高指导下，我们每个人以及自身全部的力量贡献出来。<br>  博主云：所谓的社会约定，就是每个人把自己献给全体，以获得保护自身的更大的力量。因为这个约定，个人放弃了与生俱来的无限的天然的自由，获得了社会秩序的保护，并且享有有限的自由。</p>
</li>
<li><p>唯有服从人们自己所规定的法律，才是自由，仅只有嗜欲的冲动仍然还是奴隶状态</p>
</li>
<li><p>在力量和才智上，人们虽然是不同的，但由于社会契约所规定的权利，从来都是自由和平等的。</p>
</li>
<li><p>一国之内，不能有派系存在，并且每个公民只能是表示自己的意见，才能很好地表达公意。如果有了派系存在，就应该在增加一个派系，让两个派系之间的势力保持平衡。<br>  博主云：不能有派系，也就是直接民主，像古希腊那样，适合小国；两个势均力敌的派系，也就是代议制，适合大国。至于大国，宣称唯有一个派系最合适，未之闻也。</p>
</li>
<li><p>为了保存自己的生命，每个人都有权冒着生命的危险。<br>  博主云：民不畏死，奈何以死惧之。</p>
</li>
</ul>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《禅者的初心2》读书笔记</title>
    <url>/2016/06/30/%E3%80%8A%E7%A6%85%E8%80%85%E7%9A%84%E5%88%9D%E5%BF%832%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p>　　诸行无常是你于此世遭受痛苦、挫折的原因。</p>
<p>　　如果你能缓慢地行走，没有任何想要获得什么的念头，便已经是一名优秀的禅门弟子。</p>
<p>　　入道之门屏息外界的诸多攀援之物，并止内在的情绪与妄念，当你变成一块砖或一面石墙，你便入了道。</p>
<p>　　虽然由你的推想，你可以有一种试验性的理解；然而，你应该由经验而理解空性。</p>
<p>　　叙述出来的并不是真正是实相，而当你认为它是实相时，已经包含你个人的观念和想法，那是一个自我的概念。</p>
<p>　　大小方圆，并不属于现实实相，它们仅仅是观念，这就是空去水杯的含义。（忘记已有的观念）</p>
<p>　　当我们分析一己的经验时，就产生了时间或空间大或小、重或轻的概念；某种的衡量尺度是必要的；以我们心中的各色各样的衡量尺度，我们经历着事物。然而事物本身并不存在衡量尺度，那是我们强加到实相上的。</p>
<blockquote>
<p>阮云：先生未生之前各种尺度何在，又何必头上安头。</p>
</blockquote>
<p>　　我们有无限的痛苦，因为我们有无限的欲望。</p>
<blockquote>
<p>阮云：因欲生贪，因欲生嗔，贪嗔既久，陈陈相因，愚痴无救。</p>
</blockquote>
<span id="more"></span>
<p>　　戒律不是用来约束你的，而是来支援你的修行。</p>
<p>　　如果你费很大力气去持戒，那不是真正的持戒。</p>
<p>　　真正的自由是穿着这套麻烦的正式禅袍时，不觉得被约束。</p>
<blockquote>
<p>阮云：人生而自由却不往不在枷锁之中，这些枷锁往往是自己造就。</p>
</blockquote>
<p>　　当你以全副的身心探究某事时，会生出直接的经验。</p>
<p>　　如果没有一根柱子让你攀爬，你将很难体会有朝一日从顶端跳下的经验。</p>
<p>　　你持守戒律，不是因为遵从佛陀的话语，而是要延伸真实的修行到每一天的生活中，或者在自己身上安顿自己。</p>
<blockquote>
<p>阮云：当你玩游戏时，是你在玩游戏，还是游戏在玩你。</p>
</blockquote>
<p>　　当我们说“精神的”，意指非物质的，但是根据佛教，即使是精神的，仍然是属于现象界的现实这一面。</p>
<p>　　道元禅师说，若你要离于生死，不要尝试离于生死，生死是我们这一生的配备，若无生死，我们不能生存。</p>
<p>　　有一些问题是好的，没有问题我们不能存活，但是不需要太多，你不须为自己创造更多的问题，你已经有足够的问题了。</p>
<blockquote>
<p>阮云：问题和观念一样，不要人为地制造过多。</p>
</blockquote>
<p><strong>一些感受，不一定对</strong></p>
<blockquote>
<p>百尺竿头与十尺竿头乃至一尺竿头并无甚么区别，因为都已经到了尽头，不能有所进步。<br>其实真正的竿头并不存在，只要你放下了自己。<br>只管打坐即是立足当下<br>大欲小欲就节欲来说是同一的<br>数息的目的并不是数息，方便法门而已。</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>佛学</tag>
      </tags>
  </entry>
  <entry>
    <title>《禅者的初心》读书笔记</title>
    <url>/2015/10/03/%E3%80%8A%E7%A6%85%E8%80%85%E7%9A%84%E5%88%9D%E5%BF%83%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><ul>
<li>作者：[日]铃木俊隆</li>
<li>简介：禅修的心应该始终是一颗初心（初学者的心）。初心是空空如也的，不受各种习性的羁绊。只有保持这颗初心，随时准备好去接受、怀疑，并对所有的可能性敞开，才能如实看待万物的本然面貌，一步接着一步前进，然后在一闪念中证悟到万物的原初本性。<a href="http://book.douban.com/subject/4898627/">[1]</a></li>
</ul>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><ul>
<li><p>如果你失去自足的本心，就会无戒不犯。当你的心变得苛求，当你汲汲于想要得到什么，到头来你就会违反自己誓守过的戒律，包括不妄语、不偷盗、不杀生、不邪淫等等。<br>  博主云：不忘初心，方得彼岸。</p>
</li>
<li><p>我们都会试着让自己以外的东西变得恰如其分，而不是让我们自己变得恰如其分。但是如果你自己不是恰如其分的话，也就不可能让任何东西恰如其分。</p>
</li>
<li><p>当你下定决心要以佛陀的伟大心灵来禅修时，你就会发现，最下等的马才是最有价值的。在你自身的不完美中，你会为你坚定的求道之心找到基础。那些轻轻松松就能把打坐练好的人，通常都要花更多时间才能掌握到禅的真实感和禅的精髓。但那些觉得禅修极为困难的人，却会在其中找到更多意义。所以我认为，最上等的马有时就是最下等的马，而最下等的马有时就是最上等的马。</p>
</li>
<li><p>当你说“做什么都无妨”时，实际上你是在为你做的事情找借口。<br>  博主云：大善。说“无妨”时，你已知晓所做事不好，但未能改变，故说“无妨”，给内心安慰。</p>
</li>
<li><p>修行之初，你会碰到各式各样的困难，这时你有必要做一些努力来让修行贯彻下去。对初学者而言，不需要努力的修行并非真正的修行，因为初学者的修行是需要花大力气的。尤其是对年轻人来说，必须非常刻苦耐劳才能略有所成，你必须竭尽全力。色即是色。你应该忠于自己的感觉，直到你完全忘掉你自己为止。</p>
</li>
<li><p>我们有必要记得自己做过些什么，但却不该执著于这些做过的事。<br>  博主云：不要执着，即是当下心里不要装着过去的事，不要装着未来的事。</p>
</li>
<li><p>如果你打坐累了，或者对打坐产生厌烦的感觉，就应该知道这是一个警告。那表示你的修行太理想主义，表示你有贪念，修行不够淸净。要是修行时太贪心，你就会容易气馁。</p>
</li>
<li><p>最好的一种修行，是没有快乐的感觉的（包括精神上的快乐），修行的人只管修行，忘掉了肉体与心灵的感觉，也忘掉了自身的存在，这是第四个阶段的修行，也是最高的层次。</p>
</li>
<li><p>哪怕是错误的修行，只要你知道它是错误的并持续修行下去，自然而然就会变成正确的修行。我们的修行是不可能完美的，但不必为此气馁，应该持续下去，这就是修行的秘诀所在。</p>
</li>
<li><p>要想让修行不带任何目的，有一个方法，就是限制你的活动，或者说专注于你当下的活动。<br>  博主云：如此则妄念不起，凡心不乱。</p>
</li>
<li><p>所以，我们不崇拜某种对象，而只是专注于每一个当下的活动上。叩头时叩头，打坐时打坐，吃饭时吃饭，不作他想。只要这样做，法性自然会在其中。这个在日文里称为“一修定”。“一修”是指一次的修行，“定”（即三摩地）则是专注的意思。<br>  金刚经：应如是住，如是降伏其心。</p>
</li>
<li><p>当然，有时候，某些激励是必要的，但激励只是激励，并非修行的真正目的，它只是一帖药。<br>  金刚经：知我说法如筏喻者。法尚应舍何况非法。</p>
</li>
<li><p>你应该只管磨砖，别管磨的结果，这就是我们的修行。</p>
</li>
<li><p>有什么东西梗在你的意识里头时，你就无法获得真正的从容自若。想要获得完全的从容自若，最好的方式是忘掉一切。<br>  博主云：所谓“梗”即是执着。</p>
</li>
<li><p>各位知道怎样才能带给身体休息，却不知道怎样才能带给心灵休息。哪怕是躺在床上，各位的心仍然异常忙碌，哪怕是睡着，各位的心仍忙于做梦。</p>
</li>
<li><p>当你能说出“这是虚妄”这样的话时，虚妄就会无地自容，自己走开。所以，“当在虚妄中建立修行”，不因虚妄而有所挂碍，就是修行，而即使你自己没有意识到，但这就是开悟。</p>
</li>
<li><p>佛陀说：“奇哉！奇哉！一切众生悉有如来智慧德相，唯因妄执未证。</p>
</li>
<li><p>没有刻意成为佛的时候，你就会是佛，这就是我们寻求开悟的方法。</p>
</li>
</ul>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>佛学</tag>
      </tags>
  </entry>
  <entry>
    <title>《程序员的数学》读书笔记</title>
    <url>/2016/08/14/%E3%80%8A%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E6%95%B0%E5%AD%A6%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>本书讲的的是一些程序员必须了解的数学知识，有些知识点挺有意义的，特别是对于非科班出身的程序员来说。<br>就我的观点来说，一般的程序员需要的数学水平大概是高中就够了，当然要做算法研究大学数学专业水平也不一定够。<br>下文记录本书一些有用知识点，包括一些算法的数学思路，还有有趣的逻辑问题。</p>
<span id="more"></span>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>对于一个问题的分析要注意每种情况的排他性和全体的完整性，也就是不能遗漏也不能重复。</p>
<p>逻辑蕴含<code>（A =&gt; B）</code>: 若（存在）A，则（判断）B。A为真，整个式子的值由B决定，A为假，整个式子为真。<code>A =&gt; B</code> 与 <code>￢A ∨ B</code> 等价。</p>
<p>逆否命题的真值等价与原命题。</p>
<p>摩根定律： <code>(￢A) ∨ (￢B) = ￢(A ∧ B)</code></p>
<p>余数问题一般存在周期。比如今天是周日，问第10∧2，10∧3，10∧4 … 10**n天是星期几。只要将天数对7取余数，就可得知，余数为3、2、6、4、5、1不断循环。</p>
<p>奇偶校验：就是判断奇数和偶数的个数是否符合一点的比例，比如相等。奇偶校验可以用于解决，草席是否能铺满房间的问题，信息传输过程中的完整性校验。</p>
<p>一笔画问题：其实用到了‘图’的数据结构，有n个点用线段互相连接，一个点有几根线连着，‘度’就为几。一笔画问题，如果终点和起点相同，则每个点的度都应该为偶数；如果终点和起点不同，则除起点和终点之外，每个点的度都应该为偶数；</p>
<p>排列组合中，不区分某些元素，即是除以这些元素的全排列。排列组合应该以有独一性的作为基准。</p>
<p>斐波那契数列： 今天的总数 &#x3D; 昨天的所有 + 今天生的 &#x3D; 昨天的所有 + 前天的所有。 鹦鹉螺内壁、葵花种子的摆法、植物叶子的长法、爬楼梯问题都可简化为斐波那契数列。</p>
<p>摆砖头问题： 由于每个砖头没有区别，所以它在哪个位置没有影响。</p>
<p>组合问题： n 取 k &#x3D; (n-1) 取 (k-1) + (n-1) 取 (k)。即是组合中不包含新元素、组合中包含新元素两种情况。</p>
<p>发现递归结构的要领：</p>
<ul>
<li>从n层中隐去部分问题。</li>
<li>判断剩余部分是否是n-1层的问题。</li>
</ul>
<p>不可解问题是原则上不能用程序解决的问题。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《经济学原理：微观经济学分册》思维导图</title>
    <url>/2015/10/03/%E3%80%8A%E7%BB%8F%E6%B5%8E%E5%AD%A6%E5%8E%9F%E7%90%86%EF%BC%9A%E5%BE%AE%E8%A7%82%E7%BB%8F%E6%B5%8E%E5%AD%A6%E5%88%86%E5%86%8C%E3%80%8B%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE/</url>
    <content><![CDATA[<ul>
<li>由于微观经济学经济学中的概念较多，且繁杂，故作思维导图作为记忆和理解的辅助。</li>
<li>书籍版本：<a href="http://book.douban.com/subject/3719533/">《经济学原理(第4版):微观经济学分册》</a></li>
<li>作者：<a href="http://baike.baidu.com/link?url=RCDcniLIx-hAl-nesCNmnGVNvCh8qZ4CKwB8anLMBMu58798gFgmAeRKUwsXVJ9lWJgdxYNuwdUpV-U1ly2tg_">[美] N.格里高利·曼昆</a></li>
<li>制作工具：<a href="http://www.xmind.net/">xmind</a></li>
</ul>
<!--![微观经济学-1](media/%E5%BE%AE%E8%A7%82%E7%BB%8F%E6%B5%8E%E5%AD%A6-1.jpg)-->

<p><img data-src="https://image.ponder.work/mweb/%E5%BE%AE%E8%A7%82%E7%BB%8F%E6%B5%8E%E5%AD%A6-1.jpg"></p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>思维导图</tag>
      </tags>
  </entry>
  <entry>
    <title>《美国大城市的死与生》摘录</title>
    <url>/2020/03/29/%E3%80%8A%E7%BE%8E%E5%9B%BD%E5%A4%A7%E5%9F%8E%E5%B8%82%E7%9A%84%E6%AD%BB%E4%B8%8E%E7%94%9F%E3%80%8B%E6%91%98%E5%BD%95/</url>
    <content><![CDATA[<p><em>补录，笔记太过久远，原文已然忘记。</em></p>
<p>有一种东西比公开的丑陋和混乱还要恶劣，那就是戴着一副虚伪面具，假装秩序井然，其实质是视而不见或压抑在挣扎中的要求给予关注的真实的秩序。</p>
<span id="more"></span>

<p>城市公共区域的安宁，不是主要由警察来维持的，它主要是由一个互相关联的，非正式的网络来维持的，这是一个有自觉的抑止手段的标准网络，由人们自行产生，也由其强制执行。</p>
<p>保证陌生人安全的成功街区，必须具备的三个条件：</p>
<ol>
<li>公共空间与私人空间必须界限分明</li>
<li>必须有一些天然的眼镜盯着街道，这些眼镜属于街道的天然居住者。</li>
<li>人行道上必须总有行人</li>
</ol>
<p>缺少人际交流的城市街道上会出现来历不明的陌生人</p>
<p>如果公共人物被赋予过多的负担，他们发挥的效用会急剧下降。</p>
<p>我们在理解城市的行为和了解相关城市的有用信息时，应该观察实际发生的事情，而不是进行虚无缥缈的遐想。</p>
<p>一般街区公园如果其周边环境从任何形式上都是功能单一，那么它在一天的大部分时间里不可避免地要成为真空区。</p>
<p>随着城市化进程的加快，大企业越来越大，小企业在数量上越来越多</p>
<p>就像一个街道不可能是自给自足的一样。一个地区也不可能是另一个地区的翻版；一个城市不是一些重复类似的城镇的集合体，一个有吸引力的地区应有自己的特性和特长。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《自我实现的人》读书笔记</title>
    <url>/2020/04/19/%E3%80%8A%E8%87%AA%E6%88%91%E5%AE%9E%E7%8E%B0%E7%9A%84%E4%BA%BA%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em></p>
<p>自我实现：充分利用和开发天资，能力，潜能。较少地受愿望、欲望、焦虑、恐惧的影响；或较少地受性格决定的乐观或悲观倾向的影响，对未来的预测更加准确。</p>
<p>自我实现者更倾向于领悟实际的存在而不是他们所属文化群的愿望、恐惧以及理论或信仰。</p>
<p>自我实现者的动作是发展个性，成熟发展。</p>
<p>自我实现者受自己的个性原则，而不是社会原则支配。被视为道德、伦理和价值的许多东西，可能是一般人普遍心理病态的产物。</p>
<span id="more"></span>

<p>两类自我实现者：</p>
<ol>
<li>很少有超越性体验</li>
<li>看重超越性体验</li>
</ol>
<p>无条件的积极关怀——有效心理治疗的必要先决条</p>
<p>处于高峰体验的人更受精神规律支配</p>
<p>迈向自我实现：当面对一杯酒，不要在意它的价值抑或是标志仅以它作为酒自身来评判它，倾听你自身的声音。</p>
<p>拿不准时要诚实，对自己诚实比对他人诚实重要得多。</p>
<p>每一次承担责任，都是一次自我实现。</p>
<p>自我实现是努力做好自己想做的东西。</p>
<p>识别防卫心理，寻求勇气来抛弃它们。</p>
<p>在创造力激发阶段，创造者只生活在此时此刻。</p>
<blockquote>
<p>阮云：这就是佛家所说的当下，或者说心流。</p>
</blockquote>
<p>忘我精神是发现人之真正本体，自我以及真实深刻的本质的途径。</p>
<p>约拿情节：害怕成功，对自己成长的逃避。敬慕成功的人，同时感到自惭形秽，嫉妒。</p>
<p>投射心理：我们感到别人有意使我们难堪，似乎我们成为靶子。</p>
<p>对于最高和最好事物的畏惧是固有的，是合理的。</p>
<blockquote>
<p>阮云：所以女神求而不得。</p>
</blockquote>
<p>谦卑与骄傲之间恰如其分的整合对于创造性工作是绝对必要的。</p>
<p>任何需要只要是真正地被满足，就会有助于性格的形成。<br>满足的层次：生存 - 安全感 - 归属 - 爱 - 自尊</p>
<p>越是高级的需要，对于维持纯粹的生存也就越不迫切。其满足越能更长久地推迟，并且，这种需要也越容易永远地消失。</p>
<p>高级需要的满足能引起更深刻的幸福感。</p>
<p>心理治疗不能止住饥饿（低级需要）</p>
<p>牢骚的水平可以用来表示他的生活水平的动机层次。</p>
<p>我决不应期待牢骚的停止，只应期待它们会变得越来越高级。</p>
<p>日常生活中的一般欲望，通常是达到目的的手段而非目的本身。</p>
<blockquote>
<p>阮云：比如想要一个游戏机，其实要的是玩游戏带来的满足感</p>
</blockquote>
<p>神经病症两种: </p>
<ol>
<li>需求的扭曲（代偿）</li>
<li>防止进一步伤害（防卫）</li>
</ol>
<p>治疗：帮助人们回到自我实现的轨道上来</p>
<p>提供所有必要的原料，退至一边，让机体自己表达愿望、要求，自己进行选择。</p>
<p>健康的人靠内在的法则而不是外界的压力生活。</p>
<p>成长性动机：为了长远目标保持平衡<br>匮乏性动机：降低竞争恢复平衡</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《血酬定律》读书笔记</title>
    <url>/2020/03/28/%E3%80%8A%E8%A1%80%E9%85%AC%E5%AE%9A%E5%BE%8B%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2020-03-29-15854093269717.jpg"></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>所谓血酬，即流血拼命所得的酬报，体现着生命与生存资源的交换关系。从晚清到民国，吃这碗饭的人比产业工人多得多。血酬的价值，取决于所拼抢的东西，这就是“血酬定律”。</p>
<p>血酬定律其实描述的是分配权力的权力是如何获取的，当然是通过暴力，或者祖上的暴力。</p>
<p>这书第一次读带来很大震撼，有种过于真实引起不适的感觉，慎读，慎读，慎读。</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>“帮闲”二字道出多少人情事理，他人日子过得行清闲了你便上去帮一把，忙时你道不管。</p>
<p>白员何以得之，正员于事不想出力，初找人顶替，后终成一制。</p>
<blockquote>
<p>阮云: 今谓之临时工。潜规则亦是如此，有些人觉得明规则不利于他，所以在台面下又整了一套规则出来。</p>
</blockquote>
<p>合法伤害权：即低风险伤害能力，它们价值由避免伤害的费用决定。</p>
<blockquote>
<p>阮云: 类似勒索的收益</p>
</blockquote>
<p>无恒产者无恒心，做坏事的集合成本很低。</p>
<p>低成本伤害能力，合法伤害权之类的东西，就好比是一个利薮，一块培养基，一个生态位，白员就是这个生态位的必然产物。</p>
<p>资源和劳动力总要无孔不入地流向收益比较高的领域，假如官员执法有对自己有利，这个法律就不好贯彻。</p>
<p>淘汰良民定律：富裕，狡猾的百姓得以逃脱劳役，良民或死于沟壑，或挤入白员队伍，或沦为盗贼。</p>
<p>“合法伤害权”是官家安身立命的本钱。</p>
<p>在一个秩序缺乏的社会里，对获利能力的幻想，不如加害能力的幻想，那么具有根本性。</p>
<blockquote>
<p>阮云: 所以官本位有其道理在</p>
</blockquote>
<p>施恩能不能得到回报，取决于受益者的良心，而施恩者无法控制受益者的良心；加害者可以单方面控制局面，因为加害只需依赖对方的恐惧。</p>
<p>重视意识形态和人心控制，不过是暴力赋敛集团在和平时期选择的一种低成本的统治手段。</p>
<p>潜规则体系对正规道德体系的偏离，源于从皇帝到官吏的真实行为对正式角色规定的偏离。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>历史</tag>
      </tags>
  </entry>
  <entry>
    <title>《超越自卑》读书笔记</title>
    <url>/2015/09/30/%E3%80%8A%E8%B6%85%E8%B6%8A%E8%87%AA%E5%8D%91%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-12-07-15757289828316.jpg"></p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><ul>
<li>作者：A·阿德勒</li>
<li>主要内容：讲述自卑感的形成原因以及解决方法。作者和弗洛伊德一样都认为童年对个人的心理成长有重大影响，不过着眼点有所不同，阿德勒着眼于社会合作，弗洛伊德主要研究个体、性。</li>
</ul>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><ul>
<li><p>生命意义因人而异，但每一种意义或多或少都含有错误的成分在里头。个体心理学发现生活中的每一个问题几乎都可以归到：职业、社会和性这三个主要问题之下。</p>
</li>
<li><p>人类的重要性是依他们对别人生活所做贡献而定的。</p>
</li>
<li><p>意义不是被环境所规定的，我们以我们赋予环境的意义决定了我们自己。器官缺陷、被娇纵、被忽视，最容易使人将错误的意义赋予生活。</p>
</li>
<li><p>所有心理上的错误，都是选择动作方向时的错误。</p>
<blockquote>
<p>博主云：举例说明，逃避。这种快速而又毫无意义的行为。当你遇到一件难事或者一件令你害怕的东西，它令你不安，这时我们就会逃避，这样仿佛这不安就消失了。然而你没有成长，不安的源头就一直存在，等待一个合适的时候让你触发，超出你的心理承受能力之外。为释放不安与随之而来的压力，大脑会引发一些行为进行代偿，这就是各种心理疾患的萌芽了。</p>
</blockquote>
</li>
<li><p>人类对其环境所做的改变，我们称之为文化。</p>
</li>
<li><p>感情绝不会和生活方式相对立，目标一旦订下，感情就会为了实现它而调整自身。</p>
<blockquote>
<p>博主云：如果你的做事目标仅仅是为了证明自己的能力，那么你就要小心了，因为拖延症是让一个人不无能的最快方法。</p>
</blockquote>
</li>
<li><p>愤怒是控制一个人或者一种情境的工具之一。</p>
</li>
<li><p>用有计划的打击，我们可以把任何一个儿童都打击成神经质类型的人。</p>
<blockquote>
<p>博主云：可怕的是，有些愚昧的父母，经常性地无意识地对儿童的打击，简直比计划的还可怕。</p>
</blockquote>
</li>
<li><p>倘若我们发现某种情绪很明显地引起了困难，而且违背了个人的利益，那么只想改变掉这种情绪是徒劳无益的，因为它是个人生活样式的正常表现，只有改变他的生活方式，才能斩草除根。</p>
</li>
<li><p>自卑者，我们从他的行为里可以看出他是采用什么狡诈的方法来向自己保证他的重要性</p>
</li>
<li><p>自卑者不是锻炼得更加强壮，而是使自己在自己眼中显得强壮。</p>
<blockquote>
<p>博主云：毕竟这还算得上是最快的方式，不是吗？心理问题一般是遇到问题时，我们采取了直接了当而错误的方法，直接的方法不费力，但同样没什么效果，可能还有反效果。</p>
</blockquote>
</li>
<li><p>自卑情结：当个人面对一个他无法适当应付的问题时，他表示自己跟本无法解决这个问题，此时出现的就是自卑情结的表现。</p>
<blockquote>
<p>博主云：也就是在无确实根据的情况下判断自己不行</p>
</blockquote>
</li>
<li><p>自卑感总是引起紧张，必然出现争取优越感的补偿作用，煞费苦心地避免失败，而不是追求成功。</p>
<blockquote>
<p>博主云：所以自卑加上完美主义的话，简直只有拖延症一条路可走。对个人成长来说，其实都是自我设限。</p>
</blockquote>
</li>
<li><p>没有哪个人会发现自己所处的地位已经接近最终目标——能够完全控制其所处的环境。</p>
<blockquote>
<p>博主云：无论你年龄多大阅历多么丰富，在某些事情上你都是新手。连行将就木的老人，也有他没做过的事情，就是面对死亡经历死亡。</p>
</blockquote>
</li>
<li><p>我们生活中的乐趣，主要来自生活的不确定性。</p>
<blockquote>
<p>博主云：一成不变的人生有何意义。</p>
</blockquote>
</li>
<li><p>他们（这里指有心理障碍的人）选用的方法都是正确的，没什么错误——它们都是无可厚非的。他们必须要改进的是他们的具体目标。</p>
</li>
<li><p>在卷入爱的漩涡时，有许多人觉得自己很软弱。</p>
<blockquote>
<p>博主默默看着右手。。。</p>
</blockquote>
</li>
<li><p>优越感目标，对优越感的追求是所有人类的共性。</p>
</li>
<li><p>真正能够面对并且主宰生活问题的人，是那些在奋斗过程中也能表现出利他倾向的人。</p>
</li>
<li><p>记忆绝非出自偶然，个人所记忆的是他从他接受到的无数个印象中选择出来的，那些他认为对他又重要意义的东西。</p>
</li>
<li><p>如果一个人面临的问题是他不希望用常识来解决的，那么他就可以用梦所引起的感觉，来坚持他的态度。</p>
</li>
<li><p>假如一个人无法与人交友，最大的原因可能是他想驾驭别人，事实上，他只对自己感兴趣，他的目标就是表现他个人的优越感。</p>
<blockquote>
<p>博主云：阿德勒与弗洛伊德着眼点不同，一个是社会合作，一个是个体性。</p>
</blockquote>
</li>
<li><p>在女孩子中长大的男孩子，如果不是变得非常强壮，就会变得非常软弱。</p>
<blockquote>
<p>博主云：所谓物极必反</p>
</blockquote>
</li>
<li><p>兴趣是心理功能发展的最大因素。</p>
</li>
<li><p>青春期固然存在很多危险，但它并不能真正地改变人格。</p>
</li>
<li><p>三类儿童容易产生特别困难：第一，身体有缺陷的儿童；第二，被宠坏的儿童；第三，受忽视的儿童。</p>
</li>
<li><p>在我们的文化里，大部分人在他们的困难超出某个限度后，他们的合作能力就消失殆尽了。</p>
</li>
<li><p>在16岁高中毕业时，仍然对自己未来的职业举棋不定，这些孩子常常是品学兼优的学生，但是对以后的生活没有一点点主意，这些孩子大多野心勃勃，不过却不肯真正与人合作。当我们看到这样的孩子在以后的生活中躲避困难时，我们必须以科学的方法找出他们错误的原因，并采用科学的方法纠正过来。</p>
</li>
<li><p>人类的一个非常沉重的负担就是社会上还有许多不学无术和对共同利益不感兴趣的人，这些人总觉得自己屈居人下，不如别人。</p>
</li>
<li><p>以我的观点来看，任何人的努力，只要他是以合作为最高目标的，我就完全赞同他。</p>
</li>
<li><p>强迫性的脸红，口吃，阳痿，早泄，都是对他人缺乏兴趣造成的。</p>
</li>
<li><p>假如他能成为所有人的好朋友，并以美满的婚姻和有用的工作对这些有所贡献，他就不会有自己不如别人，或是被别人击败的感觉。</p>
<blockquote>
<p>博主云：其实就是将优越感的来源转移至对他人的贡献上来，而不是以地位能力凌驾于他人之上。</p>
</blockquote>
</li>
<li><p>人类保存生命的主要方法，就是通过我们的生殖力和对肉体吸引力的不断追求，来使后代得以繁衍。</p>
</li>
<li><p>生活中的任何一种严肃而重要的工作，都是不可以先替自己安排脱身之计的。</p>
<blockquote>
<p>博主云：破釜沉舟，百二秦关终属楚。</p>
</blockquote>
</li>
</ul>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>心理学</tag>
      </tags>
  </entry>
  <entry>
    <title>《通往奴役之路》读书笔记</title>
    <url>/2019/12/20/%E3%80%8A%E9%80%9A%E5%BE%80%E5%A5%B4%E5%BD%B9%E4%B9%8B%E8%B7%AF%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>这本书应该是对三观冲击最大的一本，在大学时第一次读，先是震惊，再是愤怒，然后是害怕。</p>
<p>当然现在看来，又有一些不一样的见解。一直自认为，思维深刻，三观稳定，然而也不可避免地被各种书籍媒体塑造。</p>
<p>想要保持独立思考是何其之难，再者，就算能独立思考，人们接受的信息都是片面的，有怎能确保得出正确的结果呢？</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>如果从长远考虑，我们是自己命运的创造者，那么，从短期着眼，我们就是我们所造的观念的俘虏。</p>
<blockquote>
<p>阮云：有一些事情没什么特别的，当加上想象的动机，就变得非常可怕了。所以唯心主义也是有意义的，你的世界是有你的思想观念重构的。</p>
</blockquote>
<p>个人主义的基本特征，就是把个人当做个人尊重，就是在他自己的范围内承认他的看法和趣味是至高无上的。</p>
<blockquote>
<p>阮云：其实就是不应该承认有思想教皇的存在</p>
</blockquote>
<p>在安排我们的事务时，应该尽可能多地运用自发的社会力量，而尽可能少地借助于强制</p>
<p>“西方的”就是指自由主义与民族主义，资本主义与个人主义，自由贸易与任何形式的国际主义或对和平的热爱</p>
<p>总是使一个国家变成人间地狱的东西，恰恰是人们视图将其变成天堂。  – F·荷尔德林。</p>
<p>社会主义从一开始便直接了当地具有独裁主义的性质，是通过等级制度的路线审慎地改革社会，并强加一种强制性的“精神力量”，以此“终结革命的一种尝试”。</p>
<p>民主在自由之中寻找平等，而社会主义则在约数和奴役之中寻求平等。</p>
<p>对更大自由的许诺已经成为社会主义宣传最有效的武器之一。</p>
<p>一个名副其实的计划经济，必定有一个单一的观念。</p>
<p>防止权力专断的不是它的来源，而是对他的限制。</p>
<p>法治的基本要点：留给执掌强制权力的权行机构的行动自由，应当减少到最低程度。</p>
<p>计划必然要涉及对不同的人们的具体要求给予有意识的差别对待，并允许这个人做一定要禁止另一个人做的事情。</p>
<p>任何旨在实现公平分配的重大理想政策，必定会导致法治的破坏。</p>
<blockquote>
<p>阮云：分配的权力该如何分配</p>
</blockquote>
<p>如果我们力求获取金钱，那是因为金钱能够提供我们最广泛的选择机会去享受我们努力的结果。</p>
<p>私有制是自由的最重要保障。</p>
<p>富人得势的世界，得势的人才能致富的世界。</p>
<p>所有权力都易腐坏，绝对的权力则绝对地会腐化。</p>
<p>西方世界大多数人所想象的那种“自由社会主义”何以是纯理论的，而世界各处实行的社会主义为什么却是极权主义的。</p>
<p>目的说明手段的正当性这个原则，在集体主义道德里是至高无上的准则。</p>
<p>国家社会主义或者共产主义这一类活动背后所蕴含的道德情感的强度，也许只有历史上的伟大的宗教运动能与之相比。</p>
<p>不喜欢掌握权力的人能够当权的可能性是和一个心地非常善良的人在奴隶种植园里担任监工的工作的可能性是一样的。</p>
<p>斥责任何只为活动而活动，没有远大目标的人类行为，这是完全符合极权主义的整个精神的。</p>
<p>只有当我们对自己的利害关系负责并且只有牺牲他们的自由时，我们的决定才能有道德价值，而我们要是在没有选择自由的情况下做到了无私，在道德上也微不足道。</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>随感</tag>
      </tags>
  </entry>
  <entry>
    <title>《道德箴言录》读书笔记</title>
    <url>/2019/12/14/%E3%80%8A%E9%81%93%E5%BE%B7%E7%AE%B4%E8%A8%80%E5%BD%95%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-12-15-15763936994979.jpg"></p>
<p><em>本文为整理以前的读书笔记</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>书如其名，是作者对与道德或者说真理的思索，内容多的短小的片段。</p>
<p>虽然连贯性略微缺失，但是思想的光芒无法掩盖，适合睡前阅读。</p>
<p>从文字间能看出，作者尚未能与道融而为一，尚未知行合一，故常有无力与痛苦之感，作者难道也是个悲观主义者吗？</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>激情常常走向自己的反面</p>
<blockquote>
<p>阮云：那是因为得不到正确的发泄通路</p>
</blockquote>
<p>节制产生之因，不过害怕遭受他人的嫉妒和轻蔑</p>
<blockquote>
<p>阮云：其实更深切地是对自己所拥有的东西有不安定感</p>
</blockquote>
<p>凡物之不足大抵与过之同，但若过之而到一极限则也不存在这个问题了</p>
<blockquote>
<p>阮云：所谓过犹不及，若超越极限则是超脱，也叫能放下</p>
</blockquote>
<p>承担好运所需要的德行，要多于承担厄运所需。</p>
<p>如果我们不怀骄傲之心，我们就不会怨恨他人之骄傲。</p>
<blockquote>
<p>阮云：说明人们骄傲不能独立存在，是在与他人的比较中产生，也是着相吧</p>
</blockquote>
<p>利益使一些人盲目，使另一些人眼明</p>
<blockquote>
<p>阮云：可以用辩证法来这样造句，用马克思的话来说，是一分为二地看。这是一个万金油，随便一个名词都可以造句。</p>
</blockquote>
<p>幸福得之于体验，而非事物本身所系。</p>
<blockquote>
<p>阮云：所以啊，人生最大的痛苦莫过于，得到了想要的事物，却成为了你所讨厌的人</p>
</blockquote>
<p>真理在世间之善，并没有假冒它的伪真理所行之恶那么多</p>
<blockquote>
<p>阮云：伪真理一来于蠢，一来于坏</p>
</blockquote>
<p>优雅之于身体，犹如良知之于善良</p>
<p>没有什么东西能像“建议”那样被我们如此慷慨地送出</p>
<blockquote>
<p>阮云：戒之，戒之，好为人师</p>
</blockquote>
<p>我们太习惯于向别人伪装自己，以致最后我们向自己装自己</p>
<blockquote>
<p>阮云：这有时候也不是坏事</p>
</blockquote>
<p>人们的背叛行为更多地是出于软弱，而不是出于某种既定的背叛动机。</p>
<blockquote>
<p>阮云：所以气量是种难得的东西</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>随感</tag>
      </tags>
  </entry>
  <entry>
    <title>《随风而行》读书笔记</title>
    <url>/2016/09/04/%E3%80%8A%E9%9A%8F%E9%A3%8E%E8%80%8C%E8%A1%8C%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p><em>最近的这些读书笔记基本上都是补的以前的，其实已经近一年没怎么读文学类的书籍了，真是惭愧，自觉面目可憎。<br>工作了毕竟与大学时不同，时间也不那么充裕。而且做的是非本专业的工作，为了三餐，为了果腹，只能多看技术类的书了。<br>文学的先放一放，等稳定了再慢慢补上吧</em></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p><a href="https://book.douban.com/subject/1966573/">随风而行</a> 是伊朗作家阿巴斯·基阿鲁斯达米的一本诗集。阿巴斯·基阿鲁斯达米不仅仅是为诗人，同时他也是一位电影导演。<br>因此他的短诗有着极强的画面感，诗句隽永富含哲思，使人如临其境。</p>
<p>下面是本人从中摘录的一些喜欢的。</p>
<h2 id="摘录"><a href="#摘录" class="headerlink" title="摘录"></a>摘录</h2><p>春风不识字<br>却翻作业本<br>孩子趴在小手上<br>睡得正香</p>
<p>飞起再落下<br>落下又飞走<br>蚂蚱的方向<br>只有它知道</p>
<p>羽绒枕<br>屠害千只小鸟梦</p>
<p>怎么能<br>老龟活了三百年<br>不知有天</p>
<span id="more"></span>

<p>海一片漆黑<br>岸也一片漆黑<br>我该等太阳<br>还是月亮</p>
<p>沉睡的男人身侧<br>女人醒着<br>指望不上一只爱抚的手</p>
<p>秋日午后<br>无花果树叶<br>轻轻落下<br>停在<br>自己的影子上</p>
<p>风起时<br>轮到那片叶子<br>飘落呢</p>
<p>旅人疲惫啊<br>踽踽行<br>还有七里呢<br>是终站</p>
<p>索桥垂垂<br>擦破水面<br>弄皱了<br>月的光</p>
<p>东边<br>浓雾低垂<br>太阳<br>一轮苍白</p>
<p>修女们的辩论<br>没有一点儿结果<br>渐渐地<br>到了该睡觉的时候</p>
<p>狗儿在巷尾<br>埋伏<br>候着下一个要饭的</p>
<p>碧空<br>樱花似雨<br>染上<br>春日夕照</p>
<p>蜘蛛啊<br>两天的收获<br>让老管家的笤帚<br>全毁了</p>
<p>雨滴<br>顺玻璃滑落<br>染墨的小手<br>从窗户脸上<br>抹掉它</p>
<p>雪色茫茫<br>冒出矿井的工厂<br>刺痛了眼睛</p>
<p>越想<br>越不明白<br>为何一无所获者<br>手上尽是老茧</p>
<p>最后一片树叶在枝头坚持<br>守着承诺<br>要看一眼春天的新芽</p>
<p>火车嘶鸣着<br>停住<br>蝴蝶再铁轨上酣睡</p>
<p>明月目中疑惑<br>今天看她的人<br>是否<br>还是千年前的那些</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>《超脱烦恼的练习》笔记</title>
    <url>/2016/06/05/%E3%80%8A%E8%B6%85%E8%84%B1%E7%83%A6%E6%81%BC%E7%9A%84%E7%BB%83%E4%B9%A0%E3%80%8B/</url>
    <content><![CDATA[<p><strong>四念处观</strong></p>
<ol>
<li>身体的观察——身随念处</li>
<li>心情、感觉的观察——受随念处</li>
<li>内心的观察——心随念处</li>
<li>法则的观察——法随念处</li>
</ol>
<p>欲望和干劲是两回事，干劲反而因为欲望的压力而消耗。</p>
<blockquote>
<p>博主云：你的欲望和使欲望满足的方法往往是两个东西，所以欲望越多，越不知道该干什么。欲望想要的是一件事的结果，而我们能做的却仅仅是每一个具体的过程。</p>
</blockquote>
<p>难以抉择会给心灵带来极大负担。</p>
<blockquote>
<p>博主云：人在一段时间内的自制力是有限的，一个选择在内心停留越久，对自制力就越大损耗，自制力耗尽就表现为什么都不想做。</p>
</blockquote>
<p>欲望最原生的材料就是压力，即是苦，所以说将我们驱逐至欲望的正是（痛）苦。</p>
<p>一直待在痛苦的世界是不好的。</p>
<p>我们的欲望并不是自己的欲望，它是你前一段时间所有行为对当下心灵的残留影响。</p>
<p>往往看起来高尚伟大的“xxx主义”，大致上总说着一些漂亮的场面话，实际上只是执着于想要增加自己所属团体的利益。</p>
<p>觉得快乐并不是什么坏事，但是这往往会导致想要追求更多的快乐，就是扩大欲望。</p>
<blockquote>
<p>博主云：因快乐而生欲望，因欲望而生执着，因执着而造业。</p>
</blockquote>
<span id="more"></span>
<p>倾听对方的苦闷时，不要将自己的观点带入（无我相）。</p>
<p>所有与当下情景无关的思想念头都是杂念。心与当下该做的事情合二为一。</p>
<blockquote>
<p>金刚经云：应如是住，如是降服其心。</p>
</blockquote>
<p>一段时间内，内心积极的能量是一定的，所有消极的想法情绪都会消耗它。</p>
<p>切勿缅怀过去，亦不可期望将来的欣喜。因为过往已如云烟消逝，未来尚未发生，对当下的事物不起执着，深入观察眼前的一切，内心不受动摇，有智慧的人应该如此修行，就从今天开始努力精进。</p>
<p>刹那——一个心念启动的瞬间。</p>
<p>行动因欲望而起，过程中却也因欲望而受干扰，只要着手进行，想做的事就应该变为应做的事，从而忘掉欲望。</p>
<p>灭心中之苦即为修行。</p>
<p><strong>在行动之前花三秒钟审视欲望。</strong></p>
<p>当观知诸所以受，若过去若现在，若内若外，若粗若细，若好若丑，若远若近，悉皆非我亦非本我，以此如实正观。</p>
<p>对内心进行现行犯的机会教育。</p>
<blockquote>
<p>博主云：即是在起心动念的刹那观察念头</p>
</blockquote>
<p>如果仔细凝视自己内心的状态，会发现无论绝对非做不可、一定要做的这种念头有多强烈，其实一定还隐藏着相反的念头，只是碰巧两端之一浮出水面而已。</p>
<p><strong>色受想行识</strong></p>
<ol>
<li>色是指身体</li>
<li>受是指快乐或痛苦的感觉</li>
<li>想是指记忆知识概念</li>
<li>行是指意志、冲动</li>
<li>识是指认识、接受信息</li>
</ol>
<p>我们的心情并不是以自己的力量创造的，只是任意来去的过客罢了。</p>
<p>人类所谓的活动，是潜在中憎恶对方的快乐和希望对方痛苦两种组合而成的。</p>
<p>人的生存，是不断输入心情好、很痛苦、模棱两可这三种刺激而生存。</p>
<blockquote>
<p>博主云：感觉心情好生贪念，感觉痛苦生嗔念，感觉模棱两可即是痴亦为无明。</p>
</blockquote>
<p>用戒（自律）定（专注）慧（观察力）破贪嗔痴。</p>
<blockquote>
<p>博主云：戒而后定，定而能生慧，循序渐进，方能成就。</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>一些句子</title>
    <url>/2016/10/22/%E4%B8%80%E4%BA%9B%E5%8F%A5%E5%AD%90/</url>
    <content><![CDATA[<p>如果你不抱怨呢，你会感受到巨大的心理压力。压力有时并不是个坏东西，是的，它也许会让你感觉不舒服，但同时也是促使你进行改变的力量。</p>
<p>一旦压力减轻，人就容易维持现状。然而，如果压力没有在抱怨中流失，它就会推积起来，到达一个极限，迫使你采取行动变现状。</p>
<blockquote>
<p>其实，压力产生的后果取决与你的器量，如果你能承受那便是动力，否则只会使你崩坏。</p>
</blockquote>
<p>当遇到事情时，理智的孩子让血液进入大脑，能聪明地思考问题；野蛮的孩子让血液进入四肢，大脑空虚，疯狂冲动。</p>
<blockquote>
<p>理智虽好，但若时时都是，未免略为无趣。</p>
</blockquote>
<p>毁灭一个生命，和毁灭全世界同等罪恶；拯救一个生命，和拯救全世界同等荣耀。</p>
<blockquote>
<p>就逃跑来说，五十步和百步其实没有区别。</p>
</blockquote>
<p>当人们开始对某人进行人身攻击的时候，他才能知道自己赢得了争论。</p>
<blockquote>
<p>俗话说，君子动口不动手。</p>
</blockquote>
<span id="more"></span>

<p>外人只能看到事物的外表，即使内心充满担忧与恐惧，每个人都会在脸上装出沉着冷静的样子。</p>
<blockquote>
<p>当然你如果内心冷静，外表恐惧，别人也是看不出的。</p>
</blockquote>
<p>如果我们没有受到惩罚就意味肴我们做得正确，那么我们每天不知道要干多少坏事！</p>
<blockquote>
<p>惩罚和一件事是不是坏事没有关系，好或是坏存乎一心。</p>
</blockquote>
<p>年轻的时候决定离开一个人，就像背上行囊浪迹天涯，从形式到心情都感到特别悲壮，等到自己真正成熟的时候，才发觉离开时让人痛心的并不是某个形式，而是那种无奈的心情。</p>
<p>女人受情绪左右，男人靠欲望支配；而情绪又受环境影响，欲望则指向具体目标。</p>
<p>对于不懂女人的男人，只有先征服了世界才能再征服女人；而对于了解女人的男人，骑着自行车就直捣芳心了。</p>
<blockquote>
<p>很好的概括，然而我估计两者都没可能了，悲乎！！</p>
</blockquote>
<p>女人跟男人的对话只是一种自言自语，没有经验的男人却总是规规矩矩地去寻求客观答案。其实，二人世界里没有正确和错误，只有应该和不该。那些要坚持真理的，你不如直接去当尼采。</p>
<blockquote>
<p>有些对话只是表达情绪而不是事实，不要对情绪进行理性分析，如果能分析那便不是情绪。如果女人纯是理性，想来这世界也有些可怕。</p>
</blockquote>
<p>这个国家最让我心悸的，不是国家机器任意妄为地作恶，而是占据主流力量的普通人纷纷告诉你：这个国家就是这样的，你改变不了的，习惯了就行。</p>
<p>人不能独自生存，极致的自由意味着极致的孤独。</p>
<blockquote>
<p>弱者不能独自生存，但强者可以。</p>
</blockquote>
<p>儒以文乱法，侠以武犯禁。</p>
<blockquote>
<p>古人早就想得很明白，知识分子只能写写文章发牢骚，让他们不敢或是不能发表自己的看法，比如太祖“百花齐放”这招用得就很好。至于那些想动手的，不给他们工具就好了。</p>
</blockquote>
<p>计划经济让人体会到权力的美好。</p>
<blockquote>
<p>所谓计划，肯定要有一个实体来定，那么这里面很多活动空间了。</p>
</blockquote>
<p>人用什么方法夺得一件事物，他便极端害怕以同样的方式失去它。人因某一原因失去一件事物，他也极端害怕继续因此失去其他事物。</p>
<blockquote>
<p>因为了解，所以才更加害怕。</p>
</blockquote>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>摘录</tag>
      </tags>
  </entry>
  <entry>
    <title>事物组织的本质</title>
    <url>/2015/09/29/%E4%BA%8B%E7%89%A9%E7%BB%84%E7%BB%87%E7%9A%84%E6%9C%AC%E8%B4%A8/</url>
    <content><![CDATA[<h2 id="万物如何组织"><a href="#万物如何组织" class="headerlink" title="万物如何组织"></a>万物如何组织</h2><p><strong>区分</strong>与<strong>联系</strong>。 </p>
<ul>
<li><p>所谓区分也就是一事物仅仅是它本身而不被认为是他物，有了区分才有了物与无之间的界线，没有和其他的“区分”就可以说该事物就不能存在。</p>
</li>
<li><p>然后是联系，这体现了该事物存在的意义。如果该事物与其他事物之间完全没有关系，那么它也没有存在的必要了，因为有你没你并没有什么不同。</p>
</li>
</ul>
<!---more-->

<h2 id="如何实现“区分”"><a href="#如何实现“区分”" class="headerlink" title="如何实现“区分”"></a>如何实现“区分”</h2><p>包括以下两者：1、定义；2、作用。</p>
<ul>
<li>定义就是什么东西通过什么方式构成了它，也就是它从哪里来；</li>
<li>而作用就是能干什么也决定了它能往哪里去。</li>
</ul>
<p>在世俗，作用才是最重要的，因为你与他物的联系是由作用实现。</p>
<p>这世界就是这么势利，它第一句话总是问你：“你能为我做什么，你有什么用”。经济学告诉我们，价值是由别人对该事物未来的预期决定的，而不是它的成本。</p>
<p>而预期从何而来，根据就是一个事物的作用，一定意义上说也就是你所能带来的收益。</p>
<p>我觉得记忆（我认为学习的本质就是记忆，所谓的理解、融汇贯通），不过是构筑你脑中的世界，本质与现实世界的组织是一致的，让脑中概念各得其位。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>世界观</tag>
      </tags>
  </entry>
  <entry>
    <title>使用git的hooks自动同步代码</title>
    <url>/2016/04/16/%E4%BD%BF%E7%94%A8git%E7%9A%84hooks%E8%87%AA%E5%8A%A8%E5%90%8C%E6%AD%A5%E4%BB%A3%E7%A0%81/</url>
    <content><![CDATA[<h2 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h2><p>由于最近做Python的linux服务器脚本，经常要同步代码到测试服务器，又不想直接ssh登录到服务器，所以萌生了用git自动同步代码的想法。<br>具体的要求是：测试机是服务器，需要本地一push代码，测试机能实时更新。<br>查阅相关资料发现可以用git的hooks来实现。</p>
<span id="more"></span>

<h2 id="解决步骤"><a href="#解决步骤" class="headerlink" title="解决步骤"></a>解决步骤</h2><p>说明：  </p>
<ol>
<li>本地项目： git-test  </li>
<li>中转仓库： git-transfer  </li>
<li>目的项目： git-dest  </li>
<li>中转仓库与目的项目都在测试机上</li>
</ol>
<h3 id="初始化中转仓库"><a href="#初始化中转仓库" class="headerlink" title="初始化中转仓库"></a>初始化中转仓库</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">mkdir git-transfer</span><br><span class="line">cd git-transfer</span><br><span class="line">git --bare init</span><br></pre></td></tr></table></figure>

<h3 id="将中转仓库添加到本地项目的remote"><a href="#将中转仓库添加到本地项目的remote" class="headerlink" title="将中转仓库添加到本地项目的remote"></a>将中转仓库添加到本地项目的remote</h3><p><code>git remote add transfer ssh://r@192.168.157.129/home/r/git-transfer/</code></p>
<h3 id="给中转仓库添加hooks"><a href="#给中转仓库添加hooks" class="headerlink" title="给中转仓库添加hooks"></a>给中转仓库添加hooks</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /home/r/git-transfer/hooks/</span><br><span class="line">cp post-update.sample post-update</span><br><span class="line">vi post-update</span><br></pre></td></tr></table></figure>

<p>将 post-update 的内容修改为如下</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/sh</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"></span></span><br><span class="line"><span class="language-bash"><span class="comment"># An example hook script to prepare a packed repository for use over</span></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">dumb transports.</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"></span></span><br><span class="line"><span class="language-bash"><span class="comment"># To enable this hook, rename this file to &quot;post-update&quot;.</span></span></span><br><span class="line"></span><br><span class="line">unset GIT_DIR</span><br><span class="line">DeployPath=/home/r/git-dest</span><br><span class="line"></span><br><span class="line">cd $DeployPath</span><br><span class="line">git add . -A &amp;&amp; git stash</span><br><span class="line">git pull origin master</span><br></pre></td></tr></table></figure>

<h3 id="将中转仓库clone到目的项目"><a href="#将中转仓库clone到目的项目" class="headerlink" title="将中转仓库clone到目的项目"></a>将中转仓库clone到目的项目</h3><p><code>git clone /home/r/git-transfer/ /home/r/git-dest/</code></p>
<p>或者</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd git-dest</span><br><span class="line">git init</span><br><span class="line">git remote add origin /home/r/git-transfer/</span><br></pre></td></tr></table></figure>

<p>这样在本地项目一push，目的项目就会马上更新</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Git</tag>
      </tags>
  </entry>
  <entry>
    <title>关于Python类的继承</title>
    <url>/2016/02/28/%E5%85%B3%E4%BA%8Epython%E7%B1%BB%E7%9A%84%E7%BB%A7%E6%89%BF/</url>
    <content><![CDATA[<h2 id="引子"><a href="#引子" class="headerlink" title="引子"></a>引子</h2><p>童话里经常会看到英雄打败恶人的故事，而且故事里总会有一个类似黑暗森林的场景——要么是一个山洞，要么是一篇森林，要么是另一个星球，反正是英雄不该去的某个地方。<br>当然，一旦反面角色在剧情中出现，你就会发现英雄非得去那片破森林去杀掉坏人。当英雄的总是不得不冒着生命危险进到邪恶森林中去。</p>
<p>在面向对象编程中，“继承”就是那片邪恶森林。<br>有经验的程序员知道如何躲开这个恶魔，因为他们知道，在丛林深处的“继承”，其实是邪恶女皇“多重继承”。她喜欢用自己的巨口尖牙吃掉程序员和软件，咀嚼这些堕落者的血肉。<br>不过这片丛林的吸引力是如此的强大，几乎每一个程序员都会进去探险，梦想着提着邪恶女皇的头颅走出丛林，从而声称自己是真正的程序员。你就是无法阻止丛林的魔力，于是你深入其中，而等你冒险结束，九死一生之后，你唯一学到的，就是远远躲开这片破森林，而如果你不得不再进去一次，你会带一支军队。</p>
<p>有的程序员现在正在丛林里跟邪恶女皇作战，他会对你说你必须进到森林里去。他们这样说其实是因为他们需要你的帮助，因为他们已经无法承受他们自己创建的东西了。</p>
<p>而对于你来说，你只要记住这一条：<br>大部分使用继承的场合都可以用合成取代，而多级继承则需要不惜一切地避免之。</p>
<span id="more"></span>

<h2 id="继承-Inheritance"><a href="#继承-Inheritance" class="headerlink" title="继承(Inheritance)"></a>继承(Inheritance)</h2><p>继承的用处，就是用来指明一个类的大部分或全部功能，都是从一个父类中获得的。当你写<code>class Foo(Bar)</code>时，代码就发生了继承效果，这句代码的意思是“创建一个叫 Foo 的类，并让他继承 Bar”。</p>
<p>继承的时候，父类和子类有三种交互方式：</p>
<h3 id="隐式继承（Implicit-Inheritance）"><a href="#隐式继承（Implicit-Inheritance）" class="headerlink" title="隐式继承（Implicit Inheritance）"></a>隐式继承（Implicit Inheritance）</h3><p>当你在父类里定义了一个函数，但没有在子类中定义的例子，这时候会发生隐式继承。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">implicit</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;PARENT implicit()&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span>(<span class="title class_ inherited__">Parent</span>):</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">dad = Parent()</span><br><span class="line">son = Child()</span><br><span class="line">dad.implicit()</span><br><span class="line">son.implicit()</span><br></pre></td></tr></table></figure>

<h3 id="显式覆写（Explicit-Override）"><a href="#显式覆写（Explicit-Override）" class="headerlink" title="显式覆写（Explicit Override）"></a>显式覆写（Explicit Override）</h3><p>有时候你需要让子类里的函数有一个不同的行为，这种情况下隐式继承是做不到的，而你需要覆写子类中的函数。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">override</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;PARENT override()&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span>(<span class="title class_ inherited__">Parent</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">override</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;CHILD override()&quot;</span></span><br><span class="line"></span><br><span class="line">dad = Parent()</span><br><span class="line">son = Child()</span><br><span class="line">dad.override()</span><br><span class="line">son.override()</span><br></pre></td></tr></table></figure>

<h3 id="不完全覆写——super"><a href="#不完全覆写——super" class="headerlink" title="不完全覆写——super()"></a>不完全覆写——super()</h3><p>父类中定义的内容运行之前或者之后再修改行为。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">altered</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;PARENT altered()&quot;</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span>(<span class="title class_ inherited__">Parent</span>):</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">altered</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;CHILD, BEFORE PARENT altered()&quot;</span></span><br><span class="line">        <span class="built_in">super</span>(Child, <span class="variable language_">self</span>).altered()</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;CHILD, AFTER PARENT altered()&quot;</span></span><br><span class="line">dad = Parent()</span><br><span class="line">son = Child()</span><br><span class="line">dad.altered()</span><br><span class="line">son.altered()</span><br></pre></td></tr></table></figure>

<h2 id="合成-Composition"><a href="#合成-Composition" class="headerlink" title="合成(Composition)"></a>合成(Composition)</h2><p><strong>合成就是不通过继承直接引用所需要类的方法。</strong><br>继承是一种有用的技术，不过还有一种实现相同功能的方法，就是直接使用别的类和模块，而非依赖于继承。<br>如果你回头看的话，我们有三种继承的方式，但有两种会通过新代码取代或者修改父类的功能。这其实可以很容易地用调用模块里的函数来实现。</p>
<p>此处要注意所需类的实例化参数</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Other</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">override</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;OTHER override()&quot;</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">implicit</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;OTHER implicit()&quot;</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">altered</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;OTHER altered()&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.other = Other()</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">implicit</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.other.implicit()</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">override</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;CHILD override()&quot;</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">altered</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;CHILD, BEFORE OTHER altered()&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.other.altered()</span><br><span class="line">        <span class="built_in">print</span> <span class="string">&quot;CHILD, AFTER OTHER altered()&quot;</span></span><br><span class="line"></span><br><span class="line">son = Child()</span><br><span class="line">son.implicit()</span><br><span class="line">son.override()</span><br><span class="line">son.altered()</span><br></pre></td></tr></table></figure>

<h2 id="继承和合成的应用场合"><a href="#继承和合成的应用场合" class="headerlink" title="继承和合成的应用场合"></a>继承和合成的应用场合</h2><p>“继承 vs 合成”的问题说到底还是关于代码重用的问题。</p>
<p>你不想到处都是重复的代码，这样既难看又没效率。继承可以让你在基类里隐含父类的功能，从而解决了这个问题。而合成则是利用模块和别的类中的函数调用实现了相同的目的。</p>
<p>如果两种方案都能解决重用的问题，那什么时候该用哪个呢？这个问题答案其实是非常主观的，不过我可以给你三个大体的指引方案：</p>
<ol>
<li>不惜一切代价地避免多重继承，它带来的麻烦比能解决的问题都多。如果你非要用，那你得准备好专研类的层次结构，以及花时间去找各种东西的来龙去脉吧。</li>
<li>如果你有一些代码会在不同位置和场合应用到，那就用合成来把它们做成模块。</li>
<li>只有在代码之间有清楚的关联，可以通过一个单独的共性联系起来的时候使用继承，或者你受现有代码或者别的不可抗拒因素所限非用不可的话，那也用吧。</li>
</ol>
<p>然而，不要成为这些规则的奴隶。<br>面向对象编程中要记住的一点是，程序员创建软件包，共享代码，这些都是一种社交习俗。<br>由于这是一种社交习俗，有时可能因为你的工作同事的原因，你需要打破这些规则。这时候，你就需要去观察别人的工作方式，然后去适应这种场合。</p>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>关于生活</title>
    <url>/2016/05/22/%E5%85%B3%E4%BA%8E%E7%94%9F%E6%B4%BB/</url>
    <content><![CDATA[<p>　　有些人以为生活只有诗与远方，后来他离开了生活。<br>　　<br>　　有些人以为生活只是眼前的苟且，后来他失去了自我。</p>
<p>　　人生在世，无论理想如何远大，终须为稻粱谋。陶渊明不为五斗米折腰，前提没这点俸禄也不至于饿死。若是形式迫人，虽嗟来之食亦受之。一旦不为生计发愁，迎接你的必是诗与远方。故我先欲求之财务自由，后践吾心之所向。</p>
<p>　　不够强大的理想主义者，一大部分矮化成了现实主义者，另一部分成了随波逐流的躯壳，余下的抛弃了这个娑婆世界。<br>　　<br>　　而我，先欲求安身立命之所，后追寻最初的梦想，希望能走得更远。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
      </tags>
  </entry>
  <entry>
    <title>午睡时的梦</title>
    <url>/2020/09/19/%E5%8D%88%E7%9D%A1%E6%97%B6%E7%9A%84%E6%A2%A6/</url>
    <content><![CDATA[<p>平时例行午睡时间是下午1:20-1:50，今天也一样，只是闹钟响的时候感觉没睡够。<br>于是想再眯一会儿，然后我睡朦了，做了一个让我哭笑不得的梦。</p>
<p>下面描述下这个梦</p>
<p>在梦中，我被一阵持续的噪音惊醒，声音类似老式空调压缩机的声音，嘈杂而持久，整个房间都有一点震动的感觉。我在房间里仔细搜寻噪音的源头，好让自己再次入眠。</p>
<p>找来找去，发现我的冲牙器在不停震动，声音也很是类似，还以为忘记关开关了。于是准备把开关关了，一阵手忙脚乱开关还是没能关掉（梦中这种细致的操作不好实现？）。于是我把冲牙器的插头拔下了，但是它好像没有停下来的意思，还在不停震动着，而且冲牙器的位置也不是现实中的位置，我好似也坦然接受了这无须电源的震动（这好像不是重点），但是又无法确定噪音的源头就是它。</p>
<p>然后，富有逻辑的操作来了，我找了个桶把冲牙器盖起来，发现噪音并没有减弱，于是我排除了它的嫌疑。</p>
<p>接着就发现应该是空调的压缩机在响，我就认定是它了，然后空调的指示灯变清晰了，原来是我误开了空调，我笃定开得是制热，只有制热需要这么大的功率，空气也燥热起来，一切是那么符合逻辑。</p>
<p>心满意足找到答案之后，我醒了，发现噪音来源是外面修路的水泵。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>梦</tag>
      </tags>
  </entry>
  <entry>
    <title>图书馆见闻</title>
    <url>/2016/05/08/%E5%9B%BE%E4%B9%A6%E9%A6%86%E8%A7%81%E9%97%BB/</url>
    <content><![CDATA[<h2 id="占座党乃图书馆通病"><a href="#占座党乃图书馆通病" class="headerlink" title="占座党乃图书馆通病"></a>占座党乃图书馆通病</h2><p>　　凡是条件好一点的图书馆，只要工作人员不禁止，必定有一部分座位因占座党而闲置。<br>　　<br>　　举我最近去的广州图书馆为例，我早上9点开始自习，到下午5点，占座的人始终没来，而且不是一两次。几乎每次有占座的，很大可能一天都不来。<br>　　<br>　　这也很好理解，既然需要占座自然是来的意愿不是很强，不来自然是情理之中。人皆有惰性，无可厚非，但座位有限，还是留给需要的人为好。</p>
<h2 id="在图书馆玩手机很畅快"><a href="#在图书馆玩手机很畅快" class="headerlink" title="在图书馆玩手机很畅快"></a>在图书馆玩手机很畅快</h2><p>　　除占座党之外，手机党和电脑党也是图书馆一大特色，并且与年龄无关。<br>　　<br>　　今天坐我对面的是个大概60岁的大爷，先是早上来没找到位置坐，把我对面的占座党呵斥了一通，抢了她占的座位。然后等我下午从图书馆出来时，他手上拿的手机还没有放下。既然玩手机，在家里不是更舒服吗，何必呢？学得下就学，不行就回去休息，在这磨洋工除了求个心安之外毫无裨益。<br>　　<br>　　另外，品行、习惯与年龄没多大关系，不好的不改正，年纪大了还是不好，不过是习惯了而已。俗语说<code>三岁看老</code>，未免夸张了点，十三、二十三看老估计是十拿九稳。<br>　　<br>　　人呐，还是要对自己有所要求，若是随着年岁的增长而没有丝毫长进，多么可悲，多么可怕。年少时不学好有人教育你，年老了还无长进，无需教育也无人可教育你了。</p>
<h2 id="免费的终究会被滥用"><a href="#免费的终究会被滥用" class="headerlink" title="免费的终究会被滥用"></a>免费的终究会被滥用</h2><p>　　在卫生间，洗完手，有一人擦手用了接近2米的纸，很大一堆，不知道什么心态。如果你平时在家都是这样使用，我无话可说，但仅因为免费就滥用，未免太下作了。<br>　　<br>　　免费的东西并不是没有成本，而是一些心存善念的人承担了，因此我们在使用它的时候因心存感激。<br>　　<br>　　一切免费的物品，如图书馆的座位，如纸巾，如果不能在使用价值或者获取代价上和通常物品加以区分，就不免会被滥用，导致真正需要的人不能得到。<br>　　<br>　　<strong>而这正是这个世界的可悲之处，所有事物并不是让需要的人得到，而是让有取得优势的人得到。</strong></p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>牢骚</tag>
      </tags>
  </entry>
  <entry>
    <title>学习《重构手册》</title>
    <url>/2020/10/15/%E5%AD%A6%E4%B9%A0%E3%80%8A%E9%87%8D%E6%9E%84%E6%89%8B%E5%86%8C%E3%80%8B/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2020-10-18-%E9%87%8D%E6%9E%84%E6%89%8B%E5%86%8C-%E5%BC%82%E5%91%B3.png"></p>
<span id="more"></span>

<style type="text/css">
    .fancybox {
        display: inline-block;
        border-bottom: 0;
    }
    img[alt="before"] {
        width: 350px;
        height: 600px;
        object-fit: contain;
    }
    img[alt="after"] {
        width: 350px;
        height: 600px;
        object-fit: contain;
    }
</style>

<h2 id="类中的异味"><a href="#类中的异味" class="headerlink" title="类中的异味"></a>类中的异味</h2><h3 id="可度量的异味"><a href="#可度量的异味" class="headerlink" title="可度量的异味"></a>可度量的异味</h3><h4 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h4><p><strong>症状</strong></p>
<ul>
<li>代码中出现注释</li>
</ul>
<p><strong>原因</strong><br>作者认为某些内容没有说清楚</p>
<ul>
<li>采用某种做法的原因</li>
<li>某种复杂的算法的实现</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li>对一个代码块的注释：<a href="#Extract-Method%EF%BC%9A%E6%8A%BD%E5%8F%96%E6%96%B9%E6%B3%95">抽取方法</a>，并用注释为新方法取名</li>
<li>对方法所做工作的注释：<a href="#Rename-Method%EF%BC%9A%E9%87%8D%E5%91%BD%E5%90%8D%E6%96%B9%E6%B3%95">重命名方法</a>，用注释为方法重命名</li>
<li>注释描述的是前提条件或边界：<a href="#Introduce-Assertion%EF%BC%9A%E5%BC%95%E5%85%A5%E6%96%AD%E8%A8%80">引入断言</a></li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力，有可能暴露出重复性</p>
<p><strong>例外</strong><br>有些注释是必要的，比如需求文档的链接</p>
<h4 id="过长的方法"><a href="#过长的方法" class="headerlink" title="过长的方法"></a>过长的方法</h4><p><strong>症状</strong></p>
<ul>
<li>代码块行数很多（个人感觉超过屏幕的高度为多）</li>
</ul>
<p><strong>原因</strong><br>没有及时分解代码块，只是一味地在原来的位置增加代码</p>
<p><strong>措施</strong></p>
<ul>
<li>根据代码块的注释和空行和空格，分析前后语义，<a href="#Extract-Method%EF%BC%9A%E6%8A%BD%E5%8F%96%E6%96%B9%E6%B3%95">抽取方法</a>。</li>
<li>找到一些重构技术，用于清理直线式代码（ 即大量代码都放在一行上） 、条件式和变量</li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力，有可能暴露出重复性，通常有助于建立新的类和抽象。</p>
<p><strong>例外</strong><br>有些复杂的算法需求，天然需求很多行</p>
<h4 id="过大的类"><a href="#过大的类" class="headerlink" title="过大的类"></a>过大的类</h4><p><strong>症状</strong></p>
<ul>
<li>代码行数很多</li>
<li>大量实例变量</li>
<li>大量方法</li>
</ul>
<p><strong>原因</strong><br>没有及时根据职责拆分类，只是一味地在原来的类上增加代码。</p>
<p><strong>措施</strong></p>
<ul>
<li>优先解决过长的方法</li>
<li>一个新类能承担此类部分职责, <a href="#Extract-Class%EF%BC%9A%E6%8A%BD%E5%8F%96%E7%B1%BB">抽取类</a></li>
<li>可以划分类和新子类之间的功能的话, <a href="#Extract-Subclass%EF%BC%9A%E6%8A%BD%E5%8F%96%E5%AD%90%E7%B1%BB">抽取子类</a></li>
<li>可以确定客户所用的特性子集, <a href="#Extract-Interface%EF%BC%9A%E6%8A%BD%E5%8F%96%E6%8E%A5%E5%8F%A3">抽取接口</a></li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力，有可能暴露出重复性</p>
<p><strong>例外</strong><br>无</p>
<h4 id="过长的参数表"><a href="#过长的参数表" class="headerlink" title="过长的参数表"></a>过长的参数表</h4><p><strong>症状</strong></p>
<ul>
<li>方法有1个或2个以上的参数（个人觉得3个以上）</li>
</ul>
<p><strong>原因</strong><br>可能是为了尽量减少对象之间的耦合。这样做不是由被调用对象来了解类之间的关系，而是让调用者来确定所有一切。<br>也有可能是程序员对例程进行了通用化</p>
<p><strong>措施</strong></p>
<ul>
<li>参数可由已知对象得到：<a href="#Replace-Parameter-with-Method-Call%EF%BC%9A%E4%BB%A5%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E5%8F%96%E4%BB%A3%E5%8F%82%E6%95%B0">参数替换为方法</a>（调用方法获取该参数）</li>
<li>参数来自一个对象：<a href="#Preserve-Whole-Object%EF%BC%9A%E4%BF%9D%E6%8C%81%E5%AF%B9%E8%B1%A1%E5%AE%8C%E6%95%B4">保持对象完整</a>（传递完整对象作为参数）</li>
<li>数据不是来自一个逻辑对象：<a href="#Introduce-Parameter-Object%EF%BC%9A%E5%BC%95%E5%85%A5%E5%8F%82%E6%95%B0%E5%AF%B9%E8%B1%A1">引入参数对象</a>，将其分组（将长参数表变为少量参数对象）</li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力，有可能暴露出重复性。通常可以缩小规模。</p>
<p><strong>例外</strong></p>
<ul>
<li>不希望两个类出现依赖</li>
<li>参数不存在有意义的分组</li>
</ul>
<h3 id="命名"><a href="#命名" class="headerlink" title="命名"></a>命名</h3><h4 id="名字中包含类型"><a href="#名字中包含类型" class="headerlink" title="名字中包含类型"></a>名字中包含类型</h4><p><strong>症状</strong></p>
<ul>
<li>名字采用复合词，包含类型信息</li>
<li>匈牙利记法，如iCount</li>
<li>变量名反映的是类型，而不是其用途</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>没有ide的时代，命令包含类型增加了可读性</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li><a href="#Rename-Method%EF%BC%9A%E9%87%8D%E5%91%BD%E5%90%8D%E6%96%B9%E6%B3%95">重命名方法</a></li>
<li>重命名字段</li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力，有可能暴露出重复性</p>
<p><strong>例外</strong><br>有些场景命名中的类型是有帮助的，如sql的字段</p>
<h4 id="表达力差的名字"><a href="#表达力差的名字" class="headerlink" title="表达力差的名字"></a>表达力差的名字</h4><p><strong>症状</strong></p>
<ul>
<li>单字符或双字符</li>
<li>无元音的名字</li>
<li>带编号的变量，如panel1，panel2</li>
<li>奇怪的缩写</li>
<li>容易误导的名称</li>
</ul>
<p><strong>原因</strong><br>取名没有规范，过于随意</p>
<p><strong>措施</strong></p>
<ul>
<li><a href="#Rename-Method%EF%BC%9A%E9%87%8D%E5%91%BD%E5%90%8D%E6%96%B9%E6%B3%95">重命名方法</a></li>
<li>重命名字段</li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力</p>
<p><strong>例外</strong></p>
<ul>
<li>i,j,k等循环变量</li>
<li>只在几行代码中起作用的单字符变量</li>
<li>函数的多个版本实现</li>
</ul>
<h4 id="不一致的名字"><a href="#不一致的名字" class="headerlink" title="不一致的名字"></a>不一致的名字</h4><p><strong>症状</strong></p>
<ul>
<li>同一个对象却有多个名字</li>
</ul>
<p><strong>原因</strong><br>不同的人会在不同时刻创建类, 但作用是相同的, 导致名称不一样</p>
<p><strong>措施</strong></p>
<ul>
<li><a href="#Rename-Method%EF%BC%9A%E9%87%8D%E5%91%BD%E5%90%8D%E6%96%B9%E6%B3%95">重命名方法</a></li>
<li>重命名字段</li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力, 有可能暴露出重复性</p>
<p><strong>例外</strong><br>无</p>
<h3 id="不必要的复杂性"><a href="#不必要的复杂性" class="headerlink" title="不必要的复杂性"></a>不必要的复杂性</h3><h4 id="死代码"><a href="#死代码" class="headerlink" title="死代码"></a>死代码</h4><p><strong>症状</strong></p>
<ul>
<li>变量、参数、字段、代码段、方法或类未在任何地方使用(除了测试)</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>需求变更, 或采用了新方法, 未充分清理</li>
<li>简化代码时未发现冗余的逻辑</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li>删除相关代码并测试</li>
</ul>
<p><strong>收益</strong><br>降低规模。可以增强表述能力，代码更简单。</p>
<p><strong>例外</strong></p>
<ul>
<li>为子类或者调用方提供的hook等方法</li>
</ul>
<h4 id="过分一般性"><a href="#过分一般性" class="headerlink" title="过分一般性"></a>过分一般性</h4><p><strong>症状</strong></p>
<ul>
<li>未使用的变量、参数、字段、代码段、方法或类; 没有实际作用的继承体系和调用链</li>
<li>就当前需求来说, 代码实现过于复杂</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>预先设计, 但是实际需求并没有用到该设计, 或者不符合该设计</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li>不必要的类: <a href="#Collapse-Hierarchy%EF%BC%9A%E6%8A%98%E5%8F%A0%E7%BB%A7%E6%89%BF%E5%85%B3%E7%B3%BB">折叠继承关系</a>, <a href="#Inline-Class%EF%BC%9A%E5%86%85%E8%81%94%E7%B1%BB">内联类</a></li>
<li>不必要的方法: <a href="#Inline-Method%EF%BC%9A%E5%86%85%E8%81%94%E6%96%B9%E6%B3%95">内联方法</a>, 移除方法</li>
<li>不必要的字段: 删除</li>
<li>不必要的参数: <a href="#Remove-Parameter%EF%BC%9A%E7%A7%BB%E9%99%A4%E5%8F%82%E6%95%B0">移除参数</a></li>
</ul>
<p><strong>收益</strong><br>降低规模。可以增强表述能力，代码更简单。</p>
<p><strong>例外</strong></p>
<ul>
<li>框架代码</li>
<li>为测试提供的接口</li>
</ul>
<h3 id="重复"><a href="#重复" class="headerlink" title="重复"></a>重复</h3><h4 id="魔法数"><a href="#魔法数" class="headerlink" title="魔法数"></a>魔法数</h4><p><strong>症状</strong></p>
<ul>
<li>代码里出现了数值常量或者字符串常量</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>第一次添加常量时, 错误地认为该常量只会在该处使用</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li>特殊值: <a href="#Replace-Magic-Number-with-Symbolic-Constant%EF%BC%9A%E4%BB%A5%E7%AC%A6%E5%8F%B7%E5%B8%B8%E9%87%8F%E5%8F%96%E4%BB%A3%E9%AD%94%E6%B3%95%E6%95%B0">以符号常量取代魔法数</a></li>
<li>可根据规则生成: 替换为函数</li>
</ul>
<p><strong>收益</strong><br>减少重复。可以增强表述能力，代码更简单。</p>
<p><strong>例外</strong></p>
<ul>
<li>为了提高测试代码的可读性</li>
</ul>
<h4 id="重复代码"><a href="#重复代码" class="headerlink" title="重复代码"></a>重复代码</h4><p><strong>症状</strong></p>
<ul>
<li>简单形式: 代码实现几乎相同</li>
<li>复杂形式: 代码作用几乎相同</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>不同的程序员独立开发产生</li>
<li>懒惰, 常见于不愿意理解原代码的逻辑, 直接复制原代码少量修改</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li>两段代码部分重复: <a href="#Extract-Method%EF%BC%9A%E6%8A%BD%E5%8F%96%E6%96%B9%E6%B3%95">抽取方法</a></li>
<li>兄弟类之间的重复: 抽取公共部分, <a href="#Pull-Up-Field%EF%BC%9A%E4%B8%8A%E7%A7%BB%E5%AD%97%E6%AE%B5">上移字段</a>或<a href="#Pull-Up-Method%EF%BC%9A%E4%B8%8A%E7%A7%BB%E6%96%B9%E6%B3%95">上移方法</a></li>
<li>无关类之间的重复: 采用<a href="#Extract-Class%EF%BC%9A%E6%8A%BD%E5%8F%96%E7%B1%BB">抽取类</a>, 抽取公共部分</li>
<li>重复代码不完全相同: 采用替代算法</li>
</ul>
<p><strong>收益</strong><br>减少重复, 降低规模，代码更简单。</p>
<p><strong>例外</strong></p>
<ul>
<li>重复代码可读性更好</li>
<li>只是恰好实现相同, 代码的作用并没有什么关系</li>
</ul>
<h4 id="具有不同接口的相似类"><a href="#具有不同接口的相似类" class="headerlink" title="具有不同接口的相似类"></a>具有不同接口的相似类</h4><p><strong>症状</strong></p>
<ul>
<li>两个类的作用似乎相同, 但是使用了不同的方法名</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>开发代码时, 没有注意到已有类似代码</li>
</ul>
<p><strong>措施</strong><br>协调各个类, 使他们一致, 从而去除其中一个</p>
<ol>
<li>采用<a href="#Rename-Method%EF%BC%9A%E9%87%8D%E5%91%BD%E5%90%8D%E6%96%B9%E6%B3%95">重命名方法</a>使方法名类似。</li>
<li>使用<a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>、<a href="#Add-Parameter%EF%BC%9A%E6%B7%BB%E5%8A%A0%E5%8F%82%E6%95%B0">添加参数</a>和令方法参数化来使协议（即方法签名和实现途径）类似。</li>
<li>如果两个类只是相似而并非相同，在对它们进行合理协调后，可<a href="#Extract-Superclass%EF%BC%9A%E6%8A%BD%E5%8F%96%E8%B6%85%E7%B1%BB">抽取超类</a></li>
<li>尽量删除多余的类</li>
</ol>
<p><strong>收益</strong><br>减少重复, 降低规模，可能增强表述能力。</p>
<p><strong>例外</strong></p>
<ul>
<li>有时这些类无法修改, 比如在不同的库中</li>
</ul>
<h3 id="条件逻辑"><a href="#条件逻辑" class="headerlink" title="条件逻辑"></a>条件逻辑</h3><h4 id="Null检查"><a href="#Null检查" class="headerlink" title="Null检查"></a>Null检查</h4><p><strong>症状</strong></p>
<ul>
<li>反复出现Null检查代码</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>没有正确初始化对象和设置默认值</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li>采用默认值</li>
<li><a href="#Introduce-Null-Object%EF%BC%9A%E5%BC%95%E5%85%A5Null%E5%AF%B9%E8%B1%A1">引入Null对象</a></li>
</ul>
<p><strong>收益</strong><br>减少重复, 减少逻辑错误。</p>
<p><strong>例外</strong></p>
<ul>
<li>只出现一次的Null检查</li>
<li>Null对象的方法必须实现安全且符合逻辑的行为</li>
<li>Null有多种含义</li>
</ul>
<h4 id="复杂的布尔表达式"><a href="#复杂的布尔表达式" class="headerlink" title="复杂的布尔表达式"></a>复杂的布尔表达式</h4><p><strong>症状</strong></p>
<ul>
<li>复杂的and, or, not表达式</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>复杂的业务逻辑</li>
<li>逻辑多次修改叠加</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li><a href="https://zh.wikipedia.org/wiki/%E5%BE%B7%E6%91%A9%E6%A0%B9%E5%AE%9A%E5%BE%8B">DeMorgan法则</a>化简逻辑</li>
<li>引入解释变量</li>
<li><a href="#Replace-Nested-Conditional-with-Guard-Clauses%EF%BC%9A%E4%BB%A5%E5%8D%AB%E8%AF%AD%E5%8F%A5%E5%8F%96%E4%BB%A3%E5%B5%8C%E5%A5%97%E6%9D%A1%E4%BB%B6%E5%BC%8F">以卫语句取代嵌套条件式</a>, 提前剔除某些条件</li>
<li><a href="#Decompose-Conditional%EF%BC%9A%E5%88%86%E8%A7%A3%E6%9D%A1%E4%BB%B6%E8%A1%A8%E8%BE%BE%E5%BC%8F">分解条件表达式</a>将各个部分置于它自己的方法中。</li>
</ul>
<p><strong>收益</strong><br>可能增强表述能力</p>
<p><strong>例外</strong></p>
<ul>
<li>某些本质上复杂的逻辑, 改善不大</li>
</ul>
<h4 id="特殊用例"><a href="#特殊用例" class="headerlink" title="特殊用例"></a>特殊用例</h4><p><strong>症状</strong></p>
<ul>
<li>复杂的if语句</li>
<li>在工作前某些特定值的检查</li>
</ul>
<p><strong>原因</strong><br>没有对要判断的对象进行很好的分析和抽象</p>
<p><strong>措施</strong></p>
<ul>
<li>条件式替换为多态</li>
<li>如果过个if子句的内容类似, 修改语句使之适用于多种情况(用变量控制不同的部分)</li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力, 可能暴露重复性问题</p>
<p><strong>例外</strong></p>
<ul>
<li>递归代码的退出判断</li>
<li>有时if子句反而是最简单的</li>
</ul>
<h4 id="模拟继承-switch语句"><a href="#模拟继承-switch语句" class="headerlink" title="模拟继承(switch语句)"></a>模拟继承(switch语句)</h4><p><strong>症状</strong></p>
<ul>
<li>switch语句</li>
<li>多条if, elif</li>
<li>instanceof类型判断</li>
</ul>
<p><strong>原因</strong><br>懒得引入类型</p>
<p><strong>措施</strong><br>相同条件的switch语句多处出现</p>
<ol>
<li><a href="#Extract-Method%EF%BC%9A%E6%8A%BD%E5%8F%96%E6%96%B9%E6%B3%95">抽取方法</a>。抽出每个分支的代码</li>
<li><a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>。将相关代码搬移至适当的类</li>
<li><a href="#Replace-Type-Code-with-Subclasses%EF%BC%9A%E4%BB%A5%E5%AD%90%E7%B1%BB%E5%8F%96%E4%BB%A3%E7%B1%BB%E5%9E%8B%E7%A0%81">以子类取代类型码</a>或<a href="#Replace-Type-Code-with-State/Strategy%EF%BC%9A%E4%BB%A5%E7%8A%B6%E6%80%81/%E7%AD%96%E7%95%A5%E5%8F%96%E4%BB%A3%E7%B1%BB%E5%9E%8B%E7%A0%81">以状态&#x2F;策略取代类型码</a>。建立继承体系结构</li>
<li><a href="#Replace-Conditional-with-Polymorphism%EF%BC%9A%E4%BB%A5%E5%A4%9A%E6%80%81%E5%8F%96%E4%BB%A3%E6%9D%A1%E4%BB%B6%E8%A1%A8%E8%BE%BE%E5%BC%8F">以多态取代条件表达式</a>, 去除条件式</li>
</ol>
<p>如果条件式出现在一个单独的类中，可以通过将参数替换为显式方法或<a href="#Introduce-Null-Object%EF%BC%9A%E5%BC%95%E5%85%A5Null%E5%AF%B9%E8%B1%A1">引入Null对象</a>来取代条件逻辑。</p>
<p><strong>收益</strong><br>可以增强表述能力, 可能暴露重复性问题</p>
<p><strong>例外</strong></p>
<ul>
<li>仅出现一次的switch</li>
<li>工厂方法的switch</li>
</ul>
<h2 id="类之间的异味"><a href="#类之间的异味" class="headerlink" title="类之间的异味"></a>类之间的异味</h2><h3 id="数据"><a href="#数据" class="headerlink" title="数据"></a>数据</h3><h4 id="基本类型困扰"><a href="#基本类型困扰" class="headerlink" title="基本类型困扰"></a>基本类型困扰</h4><p><strong>症状</strong></p>
<ul>
<li>使用了基本类型或接近基本类型的类型（int,float,String,等等）</li>
<li>存在表示小整数的常量和枚举</li>
<li>存在表示字段名的字符串常量</li>
</ul>
<p><strong>原因</strong><br>当ArrayList(或其他一些通用结构）被滥用时，会出现这类紧密相关问题。</p>
<ul>
<li>没有使用类</li>
<li>模拟类型</li>
<li>模拟字段访问函数</li>
</ul>
<p><strong>措施</strong><br>对于缺失对象</p>
<ul>
<li>参见的”数据泥团”，解决数据泥团问题后通常可以封装基本类型。</li>
<li>通过<a href="#Replace-Data-Value-with-Object%EF%BC%9A%E4%BB%A5%E5%AF%B9%E8%B1%A1%E5%8F%96%E4%BB%A3%E6%95%B0%E6%8D%AE%E5%80%BC">以对象取代数据值</a>，建立首类（first-class)数据值。</li>
</ul>
<p>对于模拟类型，一个整型类型码对应一个类</p>
<ul>
<li>对于该类型码，如果行为没有条件，则它更像是一个枚举，因此可以<a href="#Replace-Type-Code-with-Class%EF%BC%9A%E4%BB%A5%E7%B1%BB%E5%8F%96%E4%BB%A3%E7%B1%BB%E5%9E%8B%E7%A0%81">以类取代类型码</a>。</li>
<li>如果此类型码是不可变的，而且该类还没有子类，则将<a href="#Replace-Type-Code-with-Subclasses%EF%BC%9A%E4%BB%A5%E5%AD%90%E7%B1%BB%E5%8F%96%E4%BB%A3%E7%B1%BB%E5%9E%8B%E7%A0%81">以子类取代类型码</a>。</li>
<li>如果类型码改变，或者类已经有子类了，就<a href="#Replace-Type-Code-with-State/Strategy%EF%BC%9A%E4%BB%A5%E7%8A%B6%E6%80%81/%E7%AD%96%E7%95%A5%E5%8F%96%E4%BB%A3%E7%B1%BB%E5%9E%8B%E7%A0%81">以状态&#x2F;策略取代类型码</a></li>
</ul>
<p>对于模拟字段访问函数</p>
<ul>
<li>如果基本类型专门用于处理某些数组元素，<a href="#Replace-Array-with-Object%EF%BC%9A%E4%BB%A5%E5%AF%B9%E8%B1%A1%E5%8F%96%E4%BB%A3%E6%95%B0%E7%BB%84">以对象取代数组</a>。</li>
</ul>
<p><strong>收益</strong><br>可以增强表述能力, 可能暴露重复性问题, 通常能表明还需要使用其他重构技术。</p>
<p><strong>例外</strong></p>
<ul>
<li>对象替换基本类型, 会增加开销</li>
</ul>
<h4 id="数据类"><a href="#数据类" class="headerlink" title="数据类"></a>数据类</h4><p><strong>症状</strong></p>
<ul>
<li>类只包含数据成员</li>
</ul>
<p><strong>原因</strong><br>类还未重复开发, 还没有抽象出行为</p>
<p><strong>措施</strong></p>
<ol>
<li>使用<a href="#Encapsulate-Field%EF%BC%9A%E5%B0%81%E8%A3%85%E5%AD%97%E6%AE%B5">封装字段</a>防止直接访问字段</li>
<li>对方法尽可能采用<a href="#Remove-Setting-Method%EF%BC%9A%E7%A7%BB%E9%99%A4%E8%AE%BE%E5%80%BC%E6%96%B9%E6%B3%95">移除设值方法</a></li>
<li>使用<a href="#Encapsulate-Collection%EF%BC%9A%E5%B0%81%E8%A3%85%E9%9B%86%E5%90%88">封装集合</a>防止直接访问任何集合类型的字段。</li>
<li>査看对象的各个客户。可以对客户使用<a href="#Extract-Method%EF%BC%9A%E6%8A%BD%E5%8F%96%E6%96%B9%E6%B3%95">抽取方法</a>，取出与类相关的代码，然后釆用<a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>将其置于类中。</li>
<li>在完成上述工作后，你可能会发现类中还有多个相似的方法。使用<a href="#Rename-Method%EF%BC%9A%E9%87%8D%E5%91%BD%E5%90%8D%E6%96%B9%E6%B3%95">重命名方法</a>、<a href="#Extract-Method%EF%BC%9A%E6%8A%BD%E5%8F%96%E6%96%B9%E6%B3%95">抽取方法</a>、<a href="#Add-Parameter%EF%BC%9A%E6%B7%BB%E5%8A%A0%E5%8F%82%E6%95%B0">添加参数</a>或<a href="#Remove-Parameter%EF%BC%9A%E7%A7%BB%E9%99%A4%E5%8F%82%E6%95%B0">移除参数</a>等重构技术来协调签名并消除重复。</li>
<li>对字段的大多数访问都不再需要了，因为搬移的方法涵盖了实际使用。此时便可以使用<a href="#Hide-Method%EF%BC%9A%E9%9A%90%E8%97%8F%E6%96%B9%E6%B3%95">隐藏方法</a>来消除对获取方法和设置方法的访问</li>
</ol>
<p><strong>收益</strong><br>可以增强表述能力，可能会暴露重复性问题</p>
<p><strong>例外</strong></p>
<ul>
<li><a href="#Encapsulate-Field%EF%BC%9A%E5%B0%81%E8%A3%85%E5%AD%97%E6%AE%B5">封装字段</a>对性能有影响</li>
</ul>
<h4 id="数据泥团"><a href="#数据泥团" class="headerlink" title="数据泥团"></a>数据泥团</h4><p><strong>症状</strong></p>
<ul>
<li>两到三个同样的项频繁地一同出现在类和参数表中。</li>
<li>在类中同时存在多组字段和方法</li>
<li>各组字段名以类似的子字符串开头或结尾</li>
</ul>
<p><strong>原因</strong><br>这些字段和方法, 往往应该属于另一个类, 但是没有人发现类缺失</p>
<p><strong>措施</strong></p>
<ol>
<li>是类的字段: 抽取字段</li>
<li>在方法签名中: [引入参数对象](Introduce #Parameter-Object：引入参数对象-, 保持参数对象完整</li>
<li>查看调用, 利用<a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>等重构方法</li>
</ol>
<p><strong>收益</strong><br>可以增强表述能力，可能会暴露重复性问题, 通常会降低规模</p>
<p><strong>例外</strong></p>
<ul>
<li>传递对象会带来额外的依赖性</li>
</ul>
<h4 id="临时字段"><a href="#临时字段" class="headerlink" title="临时字段"></a>临时字段</h4><p><strong>症状</strong></p>
<ul>
<li>字段仅在某些时候进行设置，而在其余时间为Null(或不使用）</li>
</ul>
<p><strong>原因</strong><br>通过字段而不是参数来传递信息</p>
<p><strong>措施</strong></p>
<ul>
<li><a href="#Extract-Class%EF%BC%9A%E6%8A%BD%E5%8F%96%E7%B1%BB">抽取类</a>, 移出字段及相关代码</li>
</ul>
<p><strong>收益</strong><br>增强了表述能力并提高了清晰性。可能会减少重复，特别是在其他位置可以使用新类时。</p>
<p><strong>例外</strong></p>
<ul>
<li>有时新对象不是一个有用的对象</li>
</ul>
<h3 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h3><h4 id="拒收的遗赠"><a href="#拒收的遗赠" class="headerlink" title="拒收的遗赠"></a>拒收的遗赠</h4><p><strong>症状</strong></p>
<ul>
<li>坦率的拒绝: 一个类继承自其父类，但是抛出了一个异常而不是支持一个方法</li>
<li>隐式的拒绝: 某个继承的方法不能正常工作</li>
<li>客户试图通过子类的句柄而不是父类的句柄来访问类。</li>
<li>继承没有什么实际意义，子类并不是父类的一个例子。</li>
</ul>
<p><strong>原因</strong><br>某个类之所以继承自另一个类，可能只是为了实现方便，而不是真的想用这个类来取代其父类。</p>
<p><strong>措施</strong></p>
<ul>
<li>如果不会导致混淆，可以不去管它。</li>
<li>如果找不到共享某个类关系的理由，就<a href="#Replace-Inheritance-with-Delegation%EF%BC%9A%E4%BB%A5%E5%A7%94%E6%89%98%E5%8F%96%E4%BB%A3%E7%BB%A7%E6%89%BF">以委托取代继承</a></li>
<li>如果父子关系确实有意义，则可以通过<a href="#Extract-Subclass%EF%BC%9A%E6%8A%BD%E5%8F%96%E5%AD%90%E7%B1%BB">抽取子类</a>、下移字段和下移方法来创建一个新的子类让这个类拥有非拒绝行为，并将父类的客户修改为这个新类的客户</li>
</ul>
<p><strong>收益</strong><br>可增强表述能力，能改善可测试性</p>
<p><strong>例外</strong></p>
<ul>
<li>有时拒收的遗赠可用于避免新类型的爆炸（通过拒绝特定的方法，就不需要为各种拒绝组合创建一种类型继承体系了）</li>
</ul>
<h4 id="不当的紧密性"><a href="#不当的紧密性" class="headerlink" title="不当的紧密性"></a>不当的紧密性</h4><p><strong>症状</strong></p>
<ul>
<li>一个类访问了它父类的内部（本应是私有的）部分</li>
</ul>
<p><strong>原因</strong><br>子类过分依赖了父类的一些信息, 过度耦合</p>
<p><strong>措施</strong></p>
<ul>
<li>如果子类以一种非受控方式访问父类的字段，则使用自<a href="#Encapsulate-Field%EF%BC%9A%E5%B0%81%E8%A3%85%E5%AD%97%E6%AE%B5">封装字段</a></li>
<li>如果父类可以定义一个子类能插入其中的通用算法，则使用<a href="#Form-Template-Method%EF%BC%9A%E5%A1%91%E9%80%A0%E6%A8%A1%E6%9D%BF%E5%87%BD%E6%95%B0">塑造模板函数</a></li>
<li>如果父类和子类需要进一步解耦合，则采用<a href="#Replace-Inheritance-with-Delegation%EF%BC%9A%E4%BB%A5%E5%A7%94%E6%89%98%E5%8F%96%E4%BB%A3%E7%BB%A7%E6%89%BF">以委托取代继承</a></li>
</ul>
<p><strong>收益</strong><br>可以减少重复。通常能够增强表述能力，还可能会降低规模。</p>
<p><strong>例外</strong><br>无</p>
<h4 id="懒惰类"><a href="#懒惰类" class="headerlink" title="懒惰类"></a>懒惰类</h4><p><strong>症状</strong></p>
<ul>
<li>类没有做什么工作，似乎是它的父类、子类或者调用者完成了所有相关工作</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>过度设计</li>
<li>重构过程中，类的所有职责都移到了其他位置</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li>如果一个类的父类或子类更适合用来完成该类的行为，则通过<a href="#Collapse-Hierarchy%EF%BC%9A%E6%8A%98%E5%8F%A0%E7%BB%A7%E6%89%BF%E5%85%B3%E7%B3%BB">折叠继承关系</a>将该类与其父类或子类合并。</li>
<li>否则，通过<a href="#Inline-Class%EF%BC%9A%E5%86%85%E8%81%94%E7%B1%BB">内联类</a>将其行为合并到它的调用者中。</li>
</ul>
<p><strong>收益</strong><br>可降低规模。能增强表述能力，代码更简单</p>
<p><strong>例外</strong></p>
<ul>
<li>有时使用懒惰类是为了传达意图</li>
</ul>
<h3 id="职责"><a href="#职责" class="headerlink" title="职责"></a>职责</h3><h4 id="依恋情节"><a href="#依恋情节" class="headerlink" title="依恋情节"></a>依恋情节</h4><p><strong>症状</strong></p>
<ul>
<li>一个方法似乎过于强调处理其他类的数据,而不是它自己的数据</li>
</ul>
<p><strong>原因</strong><br>常见的现象, 一般是因为代码的迭代, 类的职责发生了便宜</p>
<p><strong>措施</strong></p>
<ul>
<li>使用<a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>将动作放在适当的类中</li>
</ul>
<p><strong>收益</strong><br>可减少重复。通常会增强表述能力,暴露需重构的问题</p>
<p><strong>例外</strong></p>
<ul>
<li>策略模式或者访问者模式</li>
</ul>
<p><strong>说明</strong><br>区分依恋情结和不当的紧密性有时并不简单。<br>依恋情结是指，一个类自身所做甚少，需要借助大量其他类才能完成自己的工作。<br>不当的紧密性则是指，一个类为了访问某些本不该访问的内容，过于深入到其他类中。</p>
<h4 id="不当的紧密性-一般形式"><a href="#不当的紧密性-一般形式" class="headerlink" title="不当的紧密性(一般形式)"></a>不当的紧密性(一般形式)</h4><p><strong>症状</strong></p>
<ul>
<li>一个类访问了另一个类的内部部分(而这一部分本应是私有的)</li>
</ul>
<p><strong>原因</strong><br>两个类之间有时可能会稍有关联。等你意识到存在问题时，这两个类已经过于耦合了</p>
<p><strong>措施</strong></p>
<ul>
<li>如果两个独立的类彼此“纠缠”，则使用<a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>和搬移字段将适当的部分放在适当的类中。</li>
<li>如果纠缠的部分看上去是一个被遗漏的类，则使用<a href="#Extract-Class%EF%BC%9A%E6%8A%BD%E5%8F%96%E7%B1%BB">抽取类</a>和隐藏委托引入这个新类。</li>
<li>如果类相互指向对方，则使用将<a href="#Change-Bidirectional-Association-to-Unidirectional%EF%BC%9A%E5%B0%86%E5%8F%8C%E5%90%91%E5%85%B3%E8%81%94%E6%94%B9%E4%B8%BA%E5%8D%95%E5%90%91">将双向关联改为单向</a></li>
<li>如果子类与父类过于耦合, 参考“不当的紧密性（子类形式）”</li>
</ul>
<p><strong>收益</strong><br>可以减少重复。通常能够增强表述能力，还可能会降低规模。</p>
<p><strong>例外</strong><br>无</p>
<h4 id="消息链"><a href="#消息链" class="headerlink" title="消息链"></a>消息链</h4><p><strong>症状</strong></p>
<ul>
<li>a.b().c().d()形式的调用</li>
</ul>
<p><strong>原因</strong><br>一个对象必须与其他对象协作才能完成工作，这是自然，问题在于这不仅会使对象相互耦合，还会使获得这些对象的路径存在耦合。<br>方法不应与“陌生人”说话，也就是说，它应当只与其自身、其参数、它自己的字段或者它创建的对象有信息传递。</p>
<p><strong>措施</strong></p>
<ul>
<li>如果处理实际针对的是目标对象（即消息链最末端的对象），则使用<a href="#Extract-Method%EF%BC%9A%E6%8A%BD%E5%8F%96%E6%96%B9%E6%B3%95">抽取方法</a>和<a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>将处理置于该对象中。</li>
<li>使用隐藏委托使方法仅依赖于一个对象（因此应将d()方法置于a对象中。这可能还需要为b()和c()对象增加一个d()方法）。</li>
</ul>
<p><strong>收益</strong><br>可以减少重复或者暴露重复性问题</p>
<p><strong>例外</strong><br>如果过分应用隐藏委托，那么对象都去忙着委托，就会没有一个真正做实事的</p>
<h4 id="中间人"><a href="#中间人" class="headerlink" title="中间人"></a>中间人</h4><p><strong>症状</strong></p>
<ul>
<li>类的大多数方法都是在调用另一个对象的同一个（或类似的）方法</li>
<li>如果一个类的任务主要是将工作委托出去，这样的类就称为中间人</li>
</ul>
<p><strong>原因</strong><br>可能是因为应用了隐藏委托来解决消息链引起的。<br>可能在此之后其他一些特性已经被移除了，剩下的主要就是委托方法了。</p>
<p><strong>措施</strong></p>
<ul>
<li>一般来说，可以通过让客户直接调用委托来<a href="#Remove-Middle-Man%EF%BC%9A%E7%A7%BB%E9%99%A4%E4%B8%AD%E9%97%B4%E4%BA%BA">移除中间人</a></li>
<li>如果委托被中间人所有，或者是不可变的，而且中间人还有增加的行为，那么此中间人可以看做委托的一个示例，此时可以<a href="#Replace-Delegation-with-Inheritance%EF%BC%9A%E4%BB%A5%E7%BB%A7%E6%89%BF%E5%8F%96%E4%BB%A3%E5%A7%94%E6%89%98">以继承取代委托</a></li>
</ul>
<p><strong>收益</strong><br>可降低规模，还可能增强表述能力</p>
<p><strong>例外</strong></p>
<ul>
<li>有些模式[如代理模式（Proxy)和装饰器模式（Decorator)]会有意地创建委托</li>
<li>要在中间人和消息链之间权衡。</li>
</ul>
<h3 id="相关改变"><a href="#相关改变" class="headerlink" title="相关改变"></a>相关改变</h3><h4 id="发散式改变"><a href="#发散式改变" class="headerlink" title="发散式改变"></a>发散式改变</h4><p><strong>症状</strong></p>
<ul>
<li>尽管每次原因各异，但发现自己所修改的是同一个类</li>
</ul>
<p><strong>原因</strong><br>类在发展过程中会承担越来越多的职责，但没有人注意到这会涉及两种截然不同的决策</p>
<p><strong>措施</strong></p>
<ul>
<li>如果类既要找到对象，又要对其做一些处理，则让调用者査找对象并将该对象传入，或者让类返回调用者使用的值。</li>
<li>釆用<a href="#Extract-Class%EF%BC%9A%E6%8A%BD%E5%8F%96%E7%B1%BB">抽取类</a>为不同的决策抽取不同的类。</li>
<li>如果几个类共享相同类型的决策，则可以考虑合并这些新类(例如，通过<a href="#Extract-Superclass%EF%BC%9A%E6%8A%BD%E5%8F%96%E8%B6%85%E7%B1%BB">抽取超类</a>或<a href="#Extract-Subclass%EF%BC%9A%E6%8A%BD%E5%8F%96%E5%AD%90%E7%B1%BB">抽取子类</a>)</li>
</ul>
<p><strong>收益</strong><br>可增强表述能力（更好地传达意图），还可以提高健壮性以备将来修改。</p>
<p><strong>例外</strong><br>无</p>
<h4 id="霰弹式修改"><a href="#霰弹式修改" class="headerlink" title="霰弹式修改"></a>霰弹式修改</h4><p><strong>症状</strong></p>
<ul>
<li>仅做一个简单的修改就要更改多个类。</li>
</ul>
<p><strong>原因</strong></p>
<ul>
<li>将多个类分离是代码的一大职责。你可能缺失一个通晓全部职责的类（而大量修改本应通过这个类来完成）。</li>
<li>也有可能是因为你过度去除发散式改变而导致了这个异味。</li>
</ul>
<p><strong>措施</strong></p>
<ul>
<li>找出一个负责这些修改的类。这可能是一个现有类，也可能需要你通过应用<a href="#Extract-Class%EF%BC%9A%E6%8A%BD%E5%8F%96%E7%B1%BB">抽取类</a>来创建一个新类。</li>
<li>使用搬移字段和<a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>将功能置于所选类中。让未选中的类足够简单后，使用<a href="#Inline-Class%EF%BC%9A%E5%86%85%E8%81%94%E7%B1%BB">内联类</a>将该类去除。</li>
</ul>
<p><strong>收益</strong><br>可以减少重复，增强表达能力，并能改进可维护性（将来的修改将更为本地化）。</p>
<p><strong>例外</strong><br>无</p>
<h4 id="并行继承体系"><a href="#并行继承体系" class="headerlink" title="并行继承体系"></a>并行继承体系</h4><p><strong>症状</strong></p>
<ul>
<li>在一个继承体系中建立了一个新子类后，却发现还需要在另一个继承体系中创建一个相关的子类。</li>
<li>可能发现两个继承体系中的子类有着相同的前缀（命名可以反映出协调继承体系的需求）。</li>
<li>这是霰弹式修改的一个特例</li>
</ul>
<p><strong>原因</strong><br>这样两个类并不是无关的, 而是体现了同一决策的不同方面(维度)。</p>
<p><strong>措施</strong></p>
<ul>
<li>使用搬移字段和<a href="#Move-Method%EF%BC%9A%E6%90%AC%E7%A7%BB%E6%96%B9%E6%B3%95">搬移方法</a>来重新分配特性，以便能去除某个继承体系。</li>
</ul>
<p><strong>收益</strong><br>可以减少重复。可能会增强表述能力，也可能会降低规模</p>
<p><strong>例外</strong><br>无</p>
<h4 id="组合爆炸"><a href="#组合爆炸" class="headerlink" title="组合爆炸"></a>组合爆炸</h4><p><strong>症状</strong></p>
<ul>
<li>你本来希望只引入一个单一的新类，但是最后却发现必须在继承体系的不同位置引入多个类。</li>
<li>你发现继承体系的各个层使用了一组常见的词（例如，一层增加了样式信息，下一层增加了可变性）</li>
</ul>
<p><strong>原因</strong><br>这与并行继承体系相关，但是所有内容都折叠到了一个继承体系中 。<br>原本应当是独立的决策却通过一个继承体系实现了。</p>
<p><strong>措施</strong></p>
<ul>
<li>如果问题不严重，可以釆用<a href="#Replace-Inheritance-with-Delegation%EF%BC%9A%E4%BB%A5%E5%A7%94%E6%89%98%E5%8F%96%E4%BB%A3%E7%BB%A7%E6%89%BF">以委托取代继承</a>, 通过为各种变化提供同样的接口，可以创建一个装饰器设计模式示例。</li>
<li>如果情况已经变得相当复杂了，则有必要考虑进行大型重构了，可能还需要梳理并分解继承体系</li>
</ul>
<p><strong>收益</strong><br>可以减少重复, 降低规模</p>
<p><strong>例外</strong><br>无</p>
<h3 id="类库"><a href="#类库" class="headerlink" title="类库"></a>类库</h3><h4 id="不完备的类库"><a href="#不完备的类库" class="headerlink" title="不完备的类库"></a>不完备的类库</h4><p><strong>症状</strong></p>
<ul>
<li>你正在使用一个库类，希望该类具有某种功能，但它没有。</li>
</ul>
<p><strong>原因</strong><br>库类的作者未能满足你的要求</p>
<p><strong>措施</strong></p>
<ul>
<li>弄清类或库的所有者是否会考虑增加你所需要的支持。</li>
<li>如果仅仅是一两个方法，可以对库类的客户应用<a href="#Introduce-Foreign-Method%EF%BC%9A%E5%BC%95%E5%85%A5%E5%A4%96%E5%8A%A0%E5%87%BD%E6%95%B0">引入外加函数</a></li>
<li>如果有多个方法需要增加，则应<a href="#Introduce-Local-Extension%EF%BC%9A%E5%BC%95%E5%85%A5%E6%9C%AC%E5%9C%B0%E6%89%A9%E5%B1%95">引入本地扩展</a></li>
<li>可能需要引入一个层来覆盖这个库</li>
</ul>
<p><strong>收益</strong><br>可以减少重复</p>
<p><strong>例外</strong><br>如果有多个项目，每个项目都使用不兼容的方式来扩展一个类，那么在改变库时就会导致额外的工作</p>
<h2 id="重构技法"><a href="#重构技法" class="headerlink" title="重构技法"></a>重构技法</h2><h3 id="Composing-Methods：优化函数的构成"><a href="#Composing-Methods：优化函数的构成" class="headerlink" title="Composing Methods：优化函数的构成"></a>Composing Methods：优化函数的构成</h3><h4 id="Extract-Method：抽取方法"><a href="#Extract-Method：抽取方法" class="headerlink" title="Extract Method：抽取方法"></a>Extract Method：抽取方法</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">printOwing</span><span class="params">()</span> &#123;</span><br><span class="line">  printBanner();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Print details.</span></span><br><span class="line">  System.out.println(<span class="string">&quot;name: &quot;</span> + name);</span><br><span class="line">  System.out.println(<span class="string">&quot;amount: &quot;</span> + getOutstanding());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">printOwing</span><span class="params">()</span> &#123;</span><br><span class="line">  printBanner();</span><br><span class="line">  printDetails(getOutstanding());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">printDetails</span><span class="params">(<span class="type">double</span> outstanding)</span> &#123;</span><br><span class="line">  System.out.println(<span class="string">&quot;name: &quot;</span> + name);</span><br><span class="line">  System.out.println(<span class="string">&quot;amount: &quot;</span> + outstanding);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Inline-Method：内联方法"><a href="#Inline-Method：内联方法" class="headerlink" title="Inline Method：内联方法"></a>Inline Method：内联方法</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">PizzaDelivery</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="type">int</span> <span class="title function_">getRating</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> moreThanFiveLateDeliveries() ? <span class="number">2</span> : <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="type">boolean</span> <span class="title function_">moreThanFiveLateDeliveries</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> numberOfLateDeliveries &gt; <span class="number">5</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">PizzaDelivery</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="type">int</span> <span class="title function_">getRating</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> numberOfLateDeliveries &gt; <span class="number">5</span> ? <span class="number">2</span> : <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Extract-Variable：提炼变量"><a href="#Extract-Variable：提炼变量" class="headerlink" title="Extract Variable：提炼变量"></a>Extract Variable：提炼变量</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">renderBanner</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> ((platform.toUpperCase().indexOf(<span class="string">&quot;MAC&quot;</span>) &gt; -<span class="number">1</span>) &amp;&amp;</span><br><span class="line">       (browser.toUpperCase().indexOf(<span class="string">&quot;IE&quot;</span>) &gt; -<span class="number">1</span>) &amp;&amp;</span><br><span class="line">        wasInitialized() &amp;&amp; resize &gt; <span class="number">0</span> )</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">renderBanner</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">isMacOs</span> <span class="operator">=</span> platform.toUpperCase().indexOf(<span class="string">&quot;MAC&quot;</span>) &gt; -<span class="number">1</span>;</span><br><span class="line">  <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">isIE</span> <span class="operator">=</span> browser.toUpperCase().indexOf(<span class="string">&quot;IE&quot;</span>) &gt; -<span class="number">1</span>;</span><br><span class="line">  <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">wasResized</span> <span class="operator">=</span> resize &gt; <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (isMacOs &amp;&amp; isIE &amp;&amp; wasInitialized() &amp;&amp; wasResized) &#123;</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Inline-Temp：内联临时变量"><a href="#Inline-Temp：内联临时变量" class="headerlink" title="Inline Temp：内联临时变量"></a>Inline Temp：内联临时变量</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="title function_">hasDiscount</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">  <span class="type">double</span> <span class="variable">basePrice</span> <span class="operator">=</span> order.basePrice();</span><br><span class="line">  <span class="keyword">return</span> basePrice &gt; <span class="number">1000</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="title function_">hasDiscount</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> order.basePrice() &gt; <span class="number">1000</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Temp-with-Query：以查询取代临时变量"><a href="#Replace-Temp-with-Query：以查询取代临时变量" class="headerlink" title="Replace Temp with Query：以查询取代临时变量"></a>Replace Temp with Query：以查询取代临时变量</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">calculateTotal</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="type">double</span> <span class="variable">basePrice</span> <span class="operator">=</span> quantity * itemPrice;</span><br><span class="line">  <span class="keyword">if</span> (basePrice &gt; <span class="number">1000</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> basePrice * <span class="number">0.95</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> basePrice * <span class="number">0.98</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">calculateTotal</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (basePrice() &gt; <span class="number">1000</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> basePrice() * <span class="number">0.95</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> basePrice() * <span class="number">0.98</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">double</span> <span class="title function_">basePrice</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> quantity * itemPrice;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Split-Temporary-Variable：拆分临时变量"><a href="#Split-Temporary-Variable：拆分临时变量" class="headerlink" title="Split Temporary Variable：拆分临时变量"></a>Split Temporary Variable：拆分临时变量</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="variable">temp</span> <span class="operator">=</span> <span class="number">2</span> * (height + width);</span><br><span class="line">System.out.println(temp);</span><br><span class="line">temp = height * width;</span><br><span class="line">System.out.println(temp);</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">double</span> <span class="variable">perimeter</span> <span class="operator">=</span> <span class="number">2</span> * (height + width);</span><br><span class="line">System.out.println(perimeter);</span><br><span class="line"><span class="keyword">final</span> <span class="type">double</span> <span class="variable">area</span> <span class="operator">=</span> height * width;</span><br><span class="line">System.out.println(area);</span><br></pre></td></tr></table></figure>


<h4 id="Remove-Assignments-to-Parameters：移除参数赋值"><a href="#Remove-Assignments-to-Parameters：移除参数赋值" class="headerlink" title="Remove Assignments to Parameters：移除参数赋值"></a>Remove Assignments to Parameters：移除参数赋值</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">discount</span><span class="params">(<span class="type">int</span> inputVal, <span class="type">int</span> quantity)</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (inputVal &gt; <span class="number">50</span>) &#123;</span><br><span class="line">    inputVal -= <span class="number">2</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">discount</span><span class="params">(<span class="type">int</span> inputVal, <span class="type">int</span> quantity)</span> &#123;</span><br><span class="line">  <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> inputVal;</span><br><span class="line">  <span class="keyword">if</span> (inputVal &gt; <span class="number">50</span>) &#123;</span><br><span class="line">    result -= <span class="number">2</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Method-with-Method-Object：以方法对象替代方法"><a href="#Replace-Method-with-Method-Object：以方法对象替代方法" class="headerlink" title="Replace Method with Method Object：以方法对象替代方法"></a>Replace Method with Method Object：以方法对象替代方法</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Order</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">price</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">double</span> primaryBasePrice;</span><br><span class="line">    <span class="type">double</span> secondaryBasePrice;</span><br><span class="line">    <span class="type">double</span> tertiaryBasePrice;</span><br><span class="line">    <span class="comment">// Perform long computation.</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Order</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">price</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PriceCalculator</span>(<span class="built_in">this</span>).compute();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PriceCalculator</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">double</span> primaryBasePrice;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">double</span> secondaryBasePrice;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">double</span> tertiaryBasePrice;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">PriceCalculator</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">    <span class="comment">// Copy relevant information from the</span></span><br><span class="line">    <span class="comment">// order object.</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">compute</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// Perform long computation.</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Substitute-Algorithm：替换算法"><a href="#Substitute-Algorithm：替换算法" class="headerlink" title="Substitute Algorithm：替换算法"></a>Substitute Algorithm：替换算法</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">String <span class="title function_">foundPerson</span><span class="params">(String[] people)</span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; people.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (people[i].equals(<span class="string">&quot;Don&quot;</span>))&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;Don&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (people[i].equals(<span class="string">&quot;John&quot;</span>))&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;John&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (people[i].equals(<span class="string">&quot;Kent&quot;</span>))&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;Kent&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">String <span class="title function_">foundPerson</span><span class="params">(String[] people)</span>&#123;</span><br><span class="line">  <span class="type">List</span> <span class="variable">candidates</span> <span class="operator">=</span></span><br><span class="line">    Arrays.asList(<span class="keyword">new</span> <span class="title class_">String</span>[] &#123;<span class="string">&quot;Don&quot;</span>, <span class="string">&quot;John&quot;</span>, <span class="string">&quot;Kent&quot;</span>&#125;);</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i &lt; people.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (candidates.contains(people[i])) &#123;</span><br><span class="line">      <span class="keyword">return</span> people[i];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="Moving-Features-between-Objects：在对象之间搬移特性"><a href="#Moving-Features-between-Objects：在对象之间搬移特性" class="headerlink" title="Moving Features between Objects：在对象之间搬移特性"></a>Moving Features between Objects：在对象之间搬移特性</h3><h4 id="Move-Method：搬移方法"><a href="#Move-Method：搬移方法" class="headerlink" title="Move Method：搬移方法"></a>Move Method：搬移方法</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-move-method-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-move-method-after.png" alt="after"></p>
<h4 id="Move-Field：搬移字段"><a href="#Move-Field：搬移字段" class="headerlink" title="Move Field：搬移字段"></a>Move Field：搬移字段</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-move-field-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-move-field-after.png" alt="after"></p>
<h4 id="Extract-Class：抽取类"><a href="#Extract-Class：抽取类" class="headerlink" title="Extract Class：抽取类"></a>Extract Class：抽取类</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-extract-class-before.png" alt="before"><img data-src="https://image.ponder.work/mweb/2020-10-18-extract-class-after.png" alt="after"></p>
<h4 id="Inline-Class：内联类"><a href="#Inline-Class：内联类" class="headerlink" title="Inline Class：内联类"></a>Inline Class：内联类</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-inline-class-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-inline-class-after.png" alt="after"></p>
<h4 id="Hide-Delegate：隐藏委托"><a href="#Hide-Delegate：隐藏委托" class="headerlink" title="Hide Delegate：隐藏委托"></a>Hide Delegate：隐藏委托</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-hide-delegate-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-hide-delegate-after.png" alt="after"></p>
<h4 id="Remove-Middle-Man：移除中间人"><a href="#Remove-Middle-Man：移除中间人" class="headerlink" title="Remove Middle Man：移除中间人"></a>Remove Middle Man：移除中间人</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-remove-middle-man-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-remove-middle-man-after.png" alt="after"></p>
<h4 id="Introduce-Foreign-Method：引入外加函数"><a href="#Introduce-Foreign-Method：引入外加函数" class="headerlink" title="Introduce Foreign Method：引入外加函数"></a>Introduce Foreign Method：引入外加函数</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Report</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">sendReport</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Date</span> <span class="variable">nextDay</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>(previousEnd.getYear(),</span><br><span class="line">      previousEnd.getMonth(), previousEnd.getDate() + <span class="number">1</span>);</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Report</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">sendReport</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Date</span> <span class="variable">newStart</span> <span class="operator">=</span> nextDay(previousEnd);</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> Date <span class="title function_">nextDay</span><span class="params">(Date arg)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Date</span>(arg.getYear(), arg.getMonth(), arg.getDate() + <span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Introduce-Local-Extension：引入本地扩展"><a href="#Introduce-Local-Extension：引入本地扩展" class="headerlink" title="Introduce Local Extension：引入本地扩展"></a>Introduce Local Extension：引入本地扩展</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-introduce-local-extension-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-introduce-local-extension-after.png" alt="after"></p>
<h3 id="Organizing-Data：重新组织数据"><a href="#Organizing-Data：重新组织数据" class="headerlink" title="Organizing Data：重新组织数据"></a>Organizing Data：重新组织数据</h3><h4 id="Change-Value-to-Reference：将值对象改为引用对象"><a href="#Change-Value-to-Reference：将值对象改为引用对象" class="headerlink" title="Change Value to Reference：将值对象改为引用对象"></a>Change Value to Reference：将值对象改为引用对象</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-change-value-to-reference-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-change-value-to-reference-after.png" alt="after"></p>
<h4 id="Change-Reference-to-Value：将引用对象改为值对象"><a href="#Change-Reference-to-Value：将引用对象改为值对象" class="headerlink" title="Change Reference to Value：将引用对象改为值对象"></a>Change Reference to Value：将引用对象改为值对象</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-change-reference-to-value-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-change-reference-to-value-after.png" alt="after"></p>
<h4 id="Duplicate-Observed-Data：复制被监视数据"><a href="#Duplicate-Observed-Data：复制被监视数据" class="headerlink" title="Duplicate Observed Data：复制被监视数据"></a>Duplicate Observed Data：复制被监视数据</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-duplicate-observed-data-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-duplicate-observed-data-after.png" alt="after"></p>
<h4 id="Self-Encapsulate-Field：自封装字段"><a href="#Self-Encapsulate-Field：自封装字段" class="headerlink" title="Self Encapsulate Field：自封装字段"></a>Self Encapsulate Field：自封装字段</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Range</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">int</span> low, high;</span><br><span class="line">  <span class="type">boolean</span> <span class="title function_">includes</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> arg &gt;= low &amp;&amp; arg &lt;= high;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Range</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">int</span> low, high;</span><br><span class="line">  <span class="type">boolean</span> <span class="title function_">includes</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> arg &gt;= getLow() &amp;&amp; arg &lt;= getHigh();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="type">int</span> <span class="title function_">getLow</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> low;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="type">int</span> <span class="title function_">getHigh</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> high;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Data-Value-with-Object：以对象取代数据值"><a href="#Replace-Data-Value-with-Object：以对象取代数据值" class="headerlink" title="Replace Data Value with Object：以对象取代数据值"></a>Replace Data Value with Object：以对象取代数据值</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-replace-data-value-with-object-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-replace-data-value-with-object-after.png" alt="after"></p>
<h4 id="Replace-Array-with-Object：以对象取代数组"><a href="#Replace-Array-with-Object：以对象取代数组" class="headerlink" title="Replace Array with Object：以对象取代数组"></a>Replace Array with Object：以对象取代数组</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">String[] row = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">2</span>];</span><br><span class="line">row[<span class="number">0</span>] = <span class="string">&quot;Liverpool&quot;</span>;</span><br><span class="line">row[<span class="number">1</span>] = <span class="string">&quot;15&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Performance</span> <span class="variable">row</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Performance</span>();</span><br><span class="line">row.setName(<span class="string">&quot;Liverpool&quot;</span>);</span><br><span class="line">row.setWins(<span class="string">&quot;15&quot;</span>);</span><br></pre></td></tr></table></figure>


<h4 id="Change-Unidirectional-Association-to-Bidirectional：将单向关联改为双向"><a href="#Change-Unidirectional-Association-to-Bidirectional：将单向关联改为双向" class="headerlink" title="Change Unidirectional Association to Bidirectional：将单向关联改为双向"></a>Change Unidirectional Association to Bidirectional：将单向关联改为双向</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-change-unidirectional-association-to-bidirectional-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-change-unidirectional-association-to-bidirectional-after.png" alt="after"></p>
<h4 id="Change-Bidirectional-Association-to-Unidirectional：将双向关联改为单向"><a href="#Change-Bidirectional-Association-to-Unidirectional：将双向关联改为单向" class="headerlink" title="Change Bidirectional Association to Unidirectional：将双向关联改为单向"></a>Change Bidirectional Association to Unidirectional：将双向关联改为单向</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-change-bidirectional-association-to-unidirectional-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-change-bidirectional-association-to-unidirectional-after.png" alt="after"></p>
<h4 id="Encapsulate-Field：封装字段"><a href="#Encapsulate-Field：封装字段" class="headerlink" title="Encapsulate Field：封装字段"></a>Encapsulate Field：封装字段</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">  <span class="keyword">public</span> String name;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> name;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String arg)</span> &#123;</span><br><span class="line">    name = arg;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Encapsulate-Collection：封装集合"><a href="#Encapsulate-Collection：封装集合" class="headerlink" title="Encapsulate Collection：封装集合"></a>Encapsulate Collection：封装集合</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-encapsulate-collection-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-encapsulate-collection-after.png" alt="after"></p>
<h4 id="Replace-Magic-Number-with-Symbolic-Constant：以符号常量取代魔法数"><a href="#Replace-Magic-Number-with-Symbolic-Constant：以符号常量取代魔法数" class="headerlink" title="Replace Magic Number with Symbolic Constant：以符号常量取代魔法数"></a>Replace Magic Number with Symbolic Constant：以符号常量取代魔法数</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">potentialEnergy</span><span class="params">(<span class="type">double</span> mass, <span class="type">double</span> height)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> mass * height * <span class="number">9.81</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">double</span> <span class="variable">GRAVITATIONAL_CONSTANT</span> <span class="operator">=</span> <span class="number">9.81</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">double</span> <span class="title function_">potentialEnergy</span><span class="params">(<span class="type">double</span> mass, <span class="type">double</span> height)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> mass * height * GRAVITATIONAL_CONSTANT;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Type-Code-with-Class：以类取代类型码"><a href="#Replace-Type-Code-with-Class：以类取代类型码" class="headerlink" title="Replace Type Code with Class：以类取代类型码"></a>Replace Type Code with Class：以类取代类型码</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-replace-type-code-with-class-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-replace-type-code-with-class-after.png" alt="after"></p>
<h4 id="Replace-Type-Code-with-Subclasses：以子类取代类型码"><a href="#Replace-Type-Code-with-Subclasses：以子类取代类型码" class="headerlink" title="Replace Type Code with Subclasses：以子类取代类型码"></a>Replace Type Code with Subclasses：以子类取代类型码</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-replace-type-code-with-subclasses-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-replace-type-code-with-subclasses-after.png" alt="after"></p>
<h4 id="Replace-Type-Code-with-State-Strategy：以状态-策略取代类型码"><a href="#Replace-Type-Code-with-State-Strategy：以状态-策略取代类型码" class="headerlink" title="Replace Type Code with State&#x2F;Strategy：以状态&#x2F;策略取代类型码"></a>Replace Type Code with State&#x2F;Strategy：以状态&#x2F;策略取代类型码</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-replace-type-code-with-state-strategy-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-replace-type-code-with-state-strategy-after.png" alt="after"></p>
<h4 id="Replace-Subclass-with-Fields：以字段取代子类"><a href="#Replace-Subclass-with-Fields：以字段取代子类" class="headerlink" title="Replace Subclass with Fields：以字段取代子类"></a>Replace Subclass with Fields：以字段取代子类</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-replace-subclass-with-fields-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-replace-subclass-with-fields-after.png" alt="after"></p>
<h3 id="Simplifying-Conditional-Expressions：简化条件表达式"><a href="#Simplifying-Conditional-Expressions：简化条件表达式" class="headerlink" title="Simplifying Conditional Expressions：简化条件表达式"></a>Simplifying Conditional Expressions：简化条件表达式</h3><h4 id="Consolidate-Conditional-Expression：合并条件表达式"><a href="#Consolidate-Conditional-Expression：合并条件表达式" class="headerlink" title="Consolidate Conditional Expression：合并条件表达式"></a>Consolidate Conditional Expression：合并条件表达式</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">disabilityAmount</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (seniority &lt; <span class="number">2</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (monthsDisabled &gt; <span class="number">12</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (isPartTime) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// Compute the disability amount.</span></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">disabilityAmount</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (isNotEligibleForDisability()) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// Compute the disability amount.</span></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Consolidate-Duplicate-Conditional-Fragments：合并重复条件片断"><a href="#Consolidate-Duplicate-Conditional-Fragments：合并重复条件片断" class="headerlink" title="Consolidate Duplicate Conditional Fragments：合并重复条件片断"></a>Consolidate Duplicate Conditional Fragments：合并重复条件片断</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (isSpecialDeal()) &#123;</span><br><span class="line">  total = price * <span class="number">0.95</span>;</span><br><span class="line">  send();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">  total = price * <span class="number">0.98</span>;</span><br><span class="line">  send();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (isSpecialDeal()) &#123;</span><br><span class="line">  total = price * <span class="number">0.95</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">  total = price * <span class="number">0.98</span>;</span><br><span class="line">&#125;</span><br><span class="line">send();</span><br></pre></td></tr></table></figure>


<h4 id="Decompose-Conditional：分解条件表达式"><a href="#Decompose-Conditional：分解条件表达式" class="headerlink" title="Decompose Conditional：分解条件表达式"></a>Decompose Conditional：分解条件表达式</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (date.before(SUMMER_START) || date.after(SUMMER_END)) &#123;</span><br><span class="line">  charge = quantity * winterRate + winterServiceCharge;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">  charge = quantity * summerRate;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (isSummer(date)) &#123;</span><br><span class="line">  charge = summerCharge(quantity);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">  charge = winterCharge(quantity);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Conditional-with-Polymorphism：以多态取代条件表达式"><a href="#Replace-Conditional-with-Polymorphism：以多态取代条件表达式" class="headerlink" title="Replace Conditional with Polymorphism：以多态取代条件表达式"></a>Replace Conditional with Polymorphism：以多态取代条件表达式</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Bird</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="type">double</span> <span class="title function_">getSpeed</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> (type) &#123;</span><br><span class="line">      <span class="keyword">case</span> EUROPEAN:</span><br><span class="line">        <span class="keyword">return</span> getBaseSpeed();</span><br><span class="line">      <span class="keyword">case</span> AFRICAN:</span><br><span class="line">        <span class="keyword">return</span> getBaseSpeed() - getLoadFactor() * numberOfCoconuts;</span><br><span class="line">      <span class="keyword">case</span> NORWEGIAN_BLUE:</span><br><span class="line">        <span class="keyword">return</span> (isNailed) ? <span class="number">0</span> : getBaseSpeed(voltage);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Should be unreachable&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Bird</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">abstract</span> <span class="type">double</span> <span class="title function_">getSpeed</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">European</span> <span class="keyword">extends</span> <span class="title class_">Bird</span> &#123;</span><br><span class="line">  <span class="type">double</span> <span class="title function_">getSpeed</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> getBaseSpeed();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">African</span> <span class="keyword">extends</span> <span class="title class_">Bird</span> &#123;</span><br><span class="line">  <span class="type">double</span> <span class="title function_">getSpeed</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> getBaseSpeed() - getLoadFactor() * numberOfCoconuts;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NorwegianBlue</span> <span class="keyword">extends</span> <span class="title class_">Bird</span> &#123;</span><br><span class="line">  <span class="type">double</span> <span class="title function_">getSpeed</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (isNailed) ? <span class="number">0</span> : getBaseSpeed(voltage);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Somewhere in client code</span></span><br><span class="line">speed = bird.getSpeed();</span><br></pre></td></tr></table></figure>


<h4 id="Remove-Control-Flag：移除控制标记"><a href="#Remove-Control-Flag：移除控制标记" class="headerlink" title="Remove Control Flag：移除控制标记"></a>Remove Control Flag：移除控制标记</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">String <span class="title function_">foundMiscreant</span><span class="params">(String[] people)</span>&#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">found</span> <span class="operator">=</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; people.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (found.equals(<span class="string">&quot;&quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (people[i].equals (<span class="string">&quot;John&quot;</span>))&#123;</span><br><span class="line">                sendAlert();</span><br><span class="line">                found = <span class="string">&quot;John&quot;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> found;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">String <span class="title function_">foundMiscreant</span><span class="params">(String[] people)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; people.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (found.equals(<span class="string">&quot;&quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (people[i].equals (<span class="string">&quot;John&quot;</span>))&#123;</span><br><span class="line">                sendAlert();</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;John&quot;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Nested-Conditional-with-Guard-Clauses：以卫语句取代嵌套条件式"><a href="#Replace-Nested-Conditional-with-Guard-Clauses：以卫语句取代嵌套条件式" class="headerlink" title="Replace Nested Conditional with Guard Clauses：以卫语句取代嵌套条件式"></a>Replace Nested Conditional with Guard Clauses：以卫语句取代嵌套条件式</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">double</span> <span class="title function_">getPayAmount</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="type">double</span> result;</span><br><span class="line">  <span class="keyword">if</span> (isDead)&#123;</span><br><span class="line">    result = deadAmount();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (isSeparated)&#123;</span><br><span class="line">      result = separatedAmount();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (isRetired)&#123;</span><br><span class="line">        result = retiredAmount();</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">else</span>&#123;</span><br><span class="line">        result = normalPayAmount();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">double</span> <span class="title function_">getPayAmount</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (isDead)&#123;</span><br><span class="line">    <span class="keyword">return</span> deadAmount();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (isSeparated)&#123;</span><br><span class="line">    <span class="keyword">return</span> separatedAmount();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (isRetired)&#123;</span><br><span class="line">    <span class="keyword">return</span> retiredAmount();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> normalPayAmount();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Introduce-Null-Object：引入Null对象"><a href="#Introduce-Null-Object：引入Null对象" class="headerlink" title="Introduce Null Object：引入Null对象"></a>Introduce Null Object：引入Null对象</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (customer == <span class="literal">null</span>) &#123;</span><br><span class="line">  plan = BillingPlan.basic();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">  plan = customer.getPlan();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">NullCustomer</span> <span class="keyword">extends</span> <span class="title class_">Customer</span> &#123;</span><br><span class="line">  <span class="type">boolean</span> <span class="title function_">isNull</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  Plan <span class="title function_">getPlan</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">NullPlan</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// Some other NULL functionality.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Replace null values with Null-object.</span></span><br><span class="line">customer = (order.customer != <span class="literal">null</span>) ?</span><br><span class="line">  order.customer : <span class="keyword">new</span> <span class="title class_">NullCustomer</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Use Null-object as if it&#x27;s normal subclass.</span></span><br><span class="line">plan = customer.getPlan();</span><br></pre></td></tr></table></figure>


<h4 id="Introduce-Assertion：引入断言"><a href="#Introduce-Assertion：引入断言" class="headerlink" title="Introduce Assertion：引入断言"></a>Introduce Assertion：引入断言</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">getExpenseLimit</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="comment">// Should have either expense limit or</span></span><br><span class="line">  <span class="comment">// a primary project.</span></span><br><span class="line">  <span class="keyword">return</span> (expenseLimit != NULL_EXPENSE) ?</span><br><span class="line">    expenseLimit :</span><br><span class="line">    primaryProject.getMemberExpenseLimit();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">getExpenseLimit</span><span class="params">()</span> &#123;</span><br><span class="line">  Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (expenseLimit != NULL_EXPENSE) ?</span><br><span class="line">    expenseLimit:</span><br><span class="line">    primaryProject.getMemberExpenseLimit();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h3 id="Simplifying-Method-Calls：简化方法调用"><a href="#Simplifying-Method-Calls：简化方法调用" class="headerlink" title="Simplifying Method Calls：简化方法调用"></a>Simplifying Method Calls：简化方法调用</h3><h4 id="Add-Parameter：添加参数"><a href="#Add-Parameter：添加参数" class="headerlink" title="Add Parameter：添加参数"></a>Add Parameter：添加参数</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-add-parameter-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-add-parameter-after.png" alt="after"></p>
<h4 id="Remove-Parameter：移除参数"><a href="#Remove-Parameter：移除参数" class="headerlink" title="Remove Parameter：移除参数"></a>Remove Parameter：移除参数</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-remove-parameter-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-remove-parameter-after.png" alt="after"></p>
<h4 id="Rename-Method：重命名方法"><a href="#Rename-Method：重命名方法" class="headerlink" title="Rename Method：重命名方法"></a>Rename Method：重命名方法</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-rename-method-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-rename-method-after.png" alt="after"></p>
<h4 id="Separate-Query-from-Modifier：将查询方法和修改方法分离"><a href="#Separate-Query-from-Modifier：将查询方法和修改方法分离" class="headerlink" title="Separate Query from Modifier：将查询方法和修改方法分离"></a>Separate Query from Modifier：将查询方法和修改方法分离</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-separate-query-from-modifier-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-separate-query-from-modifier-after.png" alt="after"></p>
<h4 id="Parameterize-Method：让方法携带参数"><a href="#Parameterize-Method：让方法携带参数" class="headerlink" title="Parameterize Method：让方法携带参数"></a>Parameterize Method：让方法携带参数</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-parameterize-method-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-parameterize-method-after.png" alt="after"></p>
<h4 id="Introduce-Parameter-Object：引入参数对象"><a href="#Introduce-Parameter-Object：引入参数对象" class="headerlink" title="Introduce Parameter Object：引入参数对象"></a>Introduce Parameter Object：引入参数对象</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-introduce-parameter-object-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-introduce-parameter-object-after.png" alt="after"></p>
<h4 id="Preserve-Whole-Object：保持对象完整"><a href="#Preserve-Whole-Object：保持对象完整" class="headerlink" title="Preserve Whole Object：保持对象完整"></a>Preserve Whole Object：保持对象完整</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">low</span> <span class="operator">=</span> daysTempRange.getLow();</span><br><span class="line"><span class="type">int</span> <span class="variable">high</span> <span class="operator">=</span> daysTempRange.getHigh();</span><br><span class="line"><span class="type">boolean</span> <span class="variable">withinPlan</span> <span class="operator">=</span> plan.withinRange(low, high);</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="variable">withinPlan</span> <span class="operator">=</span> plan.withinRange(daysTempRange);</span><br></pre></td></tr></table></figure>


<h4 id="Remove-Setting-Method：移除设值方法"><a href="#Remove-Setting-Method：移除设值方法" class="headerlink" title="Remove Setting Method：移除设值方法"></a>Remove Setting Method：移除设值方法</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-remove-setting-method-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-remove-setting-method-after.png" alt="after"></p>
<h4 id="Replace-Parameter-with-Explicit-Methods：以明确函数取代参数"><a href="#Replace-Parameter-with-Explicit-Methods：以明确函数取代参数" class="headerlink" title="Replace Parameter with Explicit Methods：以明确函数取代参数"></a>Replace Parameter with Explicit Methods：以明确函数取代参数</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">setValue</span><span class="params">(String name, <span class="type">int</span> value)</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (name.equals(<span class="string">&quot;height&quot;</span>)) &#123;</span><br><span class="line">    height = value;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (name.equals(<span class="string">&quot;width&quot;</span>)) &#123;</span><br><span class="line">    width = value;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  Assert.shouldNeverReachHere();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">setHeight</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">  height = arg;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">setWidth</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">  width = arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Parameter-with-Method-Call：以函数调用取代参数"><a href="#Replace-Parameter-with-Method-Call：以函数调用取代参数" class="headerlink" title="Replace Parameter with Method Call：以函数调用取代参数"></a>Replace Parameter with Method Call：以函数调用取代参数</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">basePrice</span> <span class="operator">=</span> quantity * itemPrice;</span><br><span class="line"><span class="type">double</span> <span class="variable">seasonDiscount</span> <span class="operator">=</span> <span class="built_in">this</span>.getSeasonalDiscount();</span><br><span class="line"><span class="type">double</span> <span class="variable">fees</span> <span class="operator">=</span> <span class="built_in">this</span>.getFees();</span><br><span class="line"><span class="type">double</span> <span class="variable">finalPrice</span> <span class="operator">=</span> discountedPrice(basePrice, seasonDiscount, fees);</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">basePrice</span> <span class="operator">=</span> quantity * itemPrice;</span><br><span class="line"><span class="type">double</span> <span class="variable">finalPrice</span> <span class="operator">=</span> discountedPrice(basePrice);</span><br></pre></td></tr></table></figure>


<h4 id="Hide-Method：隐藏方法"><a href="#Hide-Method：隐藏方法" class="headerlink" title="Hide Method：隐藏方法"></a>Hide Method：隐藏方法</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-hide-method-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-hide-method-after.png" alt="after"></p>
<h4 id="Replace-Constructor-with-Factory-Method：以工厂方法取代构造函数"><a href="#Replace-Constructor-with-Factory-Method：以工厂方法取代构造函数" class="headerlink" title="Replace Constructor with Factory Method：以工厂方法取代构造函数"></a>Replace Constructor with Factory Method：以工厂方法取代构造函数</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">  Employee(<span class="type">int</span> type) &#123;</span><br><span class="line">    <span class="built_in">this</span>.type = type;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> Employee <span class="title function_">create</span><span class="params">(<span class="type">int</span> type)</span> &#123;</span><br><span class="line">    employee = <span class="keyword">new</span> <span class="title class_">Employee</span>(type);</span><br><span class="line">    <span class="comment">// do some heavy lifting.</span></span><br><span class="line">    <span class="keyword">return</span> employee;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Error-Code-with-Exception：以异常取代错误码"><a href="#Replace-Error-Code-with-Exception：以异常取代错误码" class="headerlink" title="Replace Error Code with Exception：以异常取代错误码"></a>Replace Error Code with Exception：以异常取代错误码</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">withdraw</span><span class="params">(<span class="type">int</span> amount)</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (amount &gt; _balance) &#123;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">    balance -= amount;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">withdraw</span><span class="params">(<span class="type">int</span> amount)</span> <span class="keyword">throws</span> BalanceException &#123;</span><br><span class="line">  <span class="keyword">if</span> (amount &gt; _balance) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BalanceException</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  balance -= amount;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Replace-Exception-with-Test：以测试取代异常"><a href="#Replace-Exception-with-Test：以测试取代异常" class="headerlink" title="Replace Exception with Test：以测试取代异常"></a>Replace Exception with Test：以测试取代异常</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">getValueForPeriod</span><span class="params">(<span class="type">int</span> periodNumber)</span> &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> values[periodNumber];</span><br><span class="line">  &#125; <span class="keyword">catch</span> (ArrayIndexOutOfBoundsException e) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="title function_">getValueForPeriod</span><span class="params">(<span class="type">int</span> periodNumber)</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (periodNumber &gt;= values.length) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> values[periodNumber];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h4 id="Pull-Up-Field：上移字段"><a href="#Pull-Up-Field：上移字段" class="headerlink" title="Pull Up Field：上移字段"></a>Pull Up Field：上移字段</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-pull-up-field-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-pull-up-field-after.png" alt="after"></p>
<h4 id="Pull-Up-Method：上移方法"><a href="#Pull-Up-Method：上移方法" class="headerlink" title="Pull Up Method：上移方法"></a>Pull Up Method：上移方法</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-pull-up-method-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-pull-up-method-after.png" alt="after"></p>
<h4 id="Pull-Up-Constructor-Body：上移构造函数本体"><a href="#Pull-Up-Constructor-Body：上移构造函数本体" class="headerlink" title="Pull Up Constructor Body：上移构造函数本体"></a>Pull Up Constructor Body：上移构造函数本体</h4><p>before</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Manager</span> <span class="keyword">extends</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">Manager</span><span class="params">(String name, String id, <span class="type">int</span> grade)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.name = name;</span><br><span class="line">    <span class="built_in">this</span>.id = id;</span><br><span class="line">    <span class="built_in">this</span>.grade = grade;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>after</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Manager</span> <span class="keyword">extends</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">Manager</span><span class="params">(String name, String id, <span class="type">int</span> grade)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(name, id);</span><br><span class="line">    <span class="built_in">this</span>.grade = grade;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="Dealing-with-Generalization：处理概括关系"><a href="#Dealing-with-Generalization：处理概括关系" class="headerlink" title="Dealing with Generalization：处理概括关系"></a>Dealing with Generalization：处理概括关系</h3><h4 id="Push-Down-Field：下移字段"><a href="#Push-Down-Field：下移字段" class="headerlink" title="Push Down Field：下移字段"></a>Push Down Field：下移字段</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-push-down-field-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-push-down-field-after.png" alt="after"></p>
<h4 id="Push-Down-Method：下移方法"><a href="#Push-Down-Method：下移方法" class="headerlink" title="Push Down Method：下移方法"></a>Push Down Method：下移方法</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-push-down-method-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-push-down-method-after.png" alt="after"></p>
<h4 id="Extract-Subclass：抽取子类"><a href="#Extract-Subclass：抽取子类" class="headerlink" title="Extract Subclass：抽取子类"></a>Extract Subclass：抽取子类</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-extract-subclass-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-extract-subclass-after.png" alt="after"></p>
<h4 id="Extract-Superclass：抽取超类"><a href="#Extract-Superclass：抽取超类" class="headerlink" title="Extract Superclass：抽取超类"></a>Extract Superclass：抽取超类</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-extract-superclass-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-extract-superclass-after.png" alt="after"></p>
<h4 id="Extract-Interface：抽取接口"><a href="#Extract-Interface：抽取接口" class="headerlink" title="Extract Interface：抽取接口"></a>Extract Interface：抽取接口</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-extract-interface-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-extract-interface-after.png" alt="after"></p>
<h4 id="Collapse-Hierarchy：折叠继承关系"><a href="#Collapse-Hierarchy：折叠继承关系" class="headerlink" title="Collapse Hierarchy：折叠继承关系"></a>Collapse Hierarchy：折叠继承关系</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-collapse-hierarchy-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-collapse-hierarchy-after.png" alt="after"></p>
<h4 id="Form-Template-Method：塑造模板函数"><a href="#Form-Template-Method：塑造模板函数" class="headerlink" title="Form Template Method：塑造模板函数"></a>Form Template Method：塑造模板函数</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-form-template-method-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-form-template-method-after.png" alt="after"></p>
<h4 id="Replace-Inheritance-with-Delegation：以委托取代继承"><a href="#Replace-Inheritance-with-Delegation：以委托取代继承" class="headerlink" title="Replace Inheritance with Delegation：以委托取代继承"></a>Replace Inheritance with Delegation：以委托取代继承</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-replace-inheritance-with-delegation-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-replace-inheritance-with-delegation-after.png" alt="after"></p>
<h4 id="Replace-Delegation-with-Inheritance：以继承取代委托"><a href="#Replace-Delegation-with-Inheritance：以继承取代委托" class="headerlink" title="Replace Delegation with Inheritance：以继承取代委托"></a>Replace Delegation with Inheritance：以继承取代委托</h4><p><img data-src="https://image.ponder.work/mweb/2020-10-18-replace-delegation-with-inheritance-before.png" alt="before"> <img data-src="https://image.ponder.work/mweb/2020-10-18-replace-delegation-with-inheritance-after.png" alt="after"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://book.douban.com/subject/1173730/">重构手册</a></li>
<li><a href="https://refactoring.guru/refactoring/techniques">https://refactoring.guru/refactoring/techniques</a></li>
</ol>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>重构</tag>
      </tags>
  </entry>
  <entry>
    <title>对一些事物的看法</title>
    <url>/2015/09/29/%E5%AF%B9%E4%B8%80%E4%BA%9B%E4%BA%8B%E7%89%A9%E7%9A%84%E7%9C%8B%E6%B3%95/</url>
    <content><![CDATA[<h2 id="如何理解新事物"><a href="#如何理解新事物" class="headerlink" title="如何理解新事物"></a>如何理解新事物</h2><ul>
<li>找到新旧的边界，也就是不能理解的最小单位。理解它并记住它，这样边界就往新的那个方向移动了。</li>
</ul>
<h2 id="关于智商题"><a href="#关于智商题" class="headerlink" title="关于智商题"></a>关于智商题</h2><ul>
<li>常考察思维的发散性，就是如何在有限的时间内尝试多种方法解决问题。多因素变动的组合，如图形规律题，有时是两个以上的变动叠加。</li>
</ul>
<span id="more"></span>

<h2 id="关于正念"><a href="#关于正念" class="headerlink" title="关于正念"></a>关于正念</h2><ul>
<li>正念即是改变，要从你面对的每一件事着手。</li>
<li>物来顺应，多思考正确的做法，也可以用上分析问题的思维。</li>
<li>无论事情多么繁杂，一个时间只需要面对一件事情，解决它就好啦。</li>
<li>正所谓弱水三千只取一瓢饮，其他与我何干。</li>
</ul>
<h2 id="事物发展的本质"><a href="#事物发展的本质" class="headerlink" title="事物发展的本质"></a>事物发展的本质</h2><ul>
<li>发展的本质在于变动，也就是生长与消亡的同时进行。人生亦如是，想要发展就不惧变动。</li>
</ul>
<h2 id="外界环境与自身"><a href="#外界环境与自身" class="headerlink" title="外界环境与自身"></a>外界环境与自身</h2><ul>
<li>无力者仅能改变自身，有力者能改变外界。有些无力者拼命地试图改变环境以证明自身之有力。</li>
</ul>
<h2 id="算命"><a href="#算命" class="headerlink" title="算命"></a>算命</h2><ul>
<li>算命或许是有的，观其言知其行，知言行得其性，知其性则其处事概莫能外，如何不能算哉。</li>
</ul>
<h2 id="一件事不等于它的结果"><a href="#一件事不等于它的结果" class="headerlink" title="一件事不等于它的结果"></a>一件事不等于它的结果</h2><ul>
<li>喜欢做一件事与结果何干，结果其实是另一件事了。</li>
</ul>
<h2 id="关于紧张"><a href="#关于紧张" class="headerlink" title="关于紧张"></a>关于紧张</h2><ul>
<li>紧张感让你感觉到自己的存在</li>
<li>制服紧张的办法：停下来仔细感知自己的作为。</li>
</ul>
<h2 id="做事的状态"><a href="#做事的状态" class="headerlink" title="做事的状态"></a>做事的状态</h2><ul>
<li>解决没有状态的办法不是等待，而是继续做，状态自然到来。对一个事物从不喜欢到喜欢，时间的投入是最简单的方法，或许是因为‘沉没成本’的增加。</li>
</ul>
<h2 id="慈善、布施"><a href="#慈善、布施" class="headerlink" title="慈善、布施"></a>慈善、布施</h2><ul>
<li>解脱众生者，实为本心求解脱。</li>
</ul>
<h2 id="标准"><a href="#标准" class="headerlink" title="标准"></a>标准</h2><ul>
<li>人无法准确衡量新事物的价值，因为没有标准。所以很多愚蠢的事发生就不奇怪了，因为发生了才明白。</li>
</ul>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>世界观</tag>
      </tags>
  </entry>
  <entry>
    <title>《庄子諵哗》读书笔记</title>
    <url>/2020/08/01/%E5%BA%84%E5%AD%90%E8%AB%B5%E5%93%97-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p>从高中接触庄子开始，就觉得太难懂了，无论原著还是翻译。直到大学发现南老师讲的这本，才算是勉强能看下去。</p>
<p>从14年开始，晚上时不时看一点，到现在看完，正好六年。</p>
<p>人生又有多少个六年呢</p>
<span id="more"></span>

<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><p>第一个主题，就是人生要“具见”，见地具备，就是普通讲的见解，再普通一点讲，就是眼光、思想。</p>
<p>物与物之间互相在变化，所以叫“物化”</p>
<blockquote>
<p>阮云：庄周化蝶，就是物化</p>
</blockquote>
<p>“野马”不是一匹马喔，“野马”就是佛经上讲的“阳焰”，太阳光的幻影，古书叫做“海市蜃楼”。</p>
<p>人类的见解、知识和生活经验都是“比量”，不是真实的。同样一个气候，同样一个空间，一个时间，一个颜色，因人而产生的感受各异。</p>
<blockquote>
<p>阮云：因为人和真实的世界，中间总是隔着一层感官，所以每个人的感受是不一样的。</p>
</blockquote>
<p>“循业发现”。每一个人根据他自己的生活经历、思想见解、智慧境界等，看一个东西的观念都不同。</p>
<blockquote>
<p>阮云：这也是为什么人们很难相互理解</p>
</blockquote>
<p>世味尝来浑是蜡，莫教开口向人提。</p>
<blockquote>
<p>阮云：大抵是真实而又乏味吧，而且生活的苦不要对他人讲，因为别人体会不了，徒增笑料</p>
</blockquote>
<p>依他而起，属于“比量”的境界，通过比较而产生的，都是“依他而起”</p>
<blockquote>
<p>阮云：人类的见解、知识和生活经验都是“依他而起”，这个“他”，类似佛家的本性</p>
</blockquote>
<p>以神仙丹道家学说来讲，认为生时魄在肉体生命活力中普遍存在。不经过修炼，不能和魂凝聚为一，死后魄就归沉于地。因此，魂就是鬼影，魄是鬼形。</p>
<p>人世间哪个是真理？哪个是是？哪个是非？哪个是黑？哪个是白？其实对与不对，都是人的“师心自用”。就是说一个人有“成见”，有主观的观念，自以为对就对，叫“师心自用”。</p>
<blockquote>
<p>阮云：人总以为自己的经验，能够放之四海，可笑啊</p>
</blockquote>
<p>会万物于己者，其惟圣人乎。</p>
<blockquote>
<p>阮云：所谓物我混而为一</p>
</blockquote>
<p>庄子说：是非观念所产生可以不可以，是从我们的主观来的，我们的认识，你认为可以就可以，你认为不可以就不可以，宇宙间没有一个真正的离开身心以外的是非观念。</p>
<blockquote>
<p>阮云：所以说，真正的客观是不存在的。</p>
</blockquote>
<p>“物固有所然，”天地万物它有它的所以然，既然宇宙形成了万物，电就是电，电通过灯时，它发亮；通过录音机收音机时，它发声。这个物体有它所以然的特别的性能。“物固有所可。”所以万物有它适宜应该的本位，有它适宜应该的立场。</p>
<blockquote>
<p>阮云：一个物有造就它的原因，自然也限制了它能适用的地方。如果这些变了，这物就变为他物了吧</p>
</blockquote>
<p>“不用而寓诸庸”呢？“庸”不是马虎，不是差不多，是“得其环中”，</p>
<blockquote>
<p>阮云：无用之用是谓大用，道家来讲是无为，佛家是随缘</p>
</blockquote>
<p>把自己的精神、聪明向一点上钻。这个“劳神明为一”的“一”，不是“道通为一”的“一”的意思，不要搞错了。只向一点上钻牛角尖，他认为自己最高明，不晓得向大同方面钻，</p>
<p>形而上之道无是也无非，无善也无恶，形而下之道，有是非，有善恶。</p>
<blockquote>
<p>阮云：世界万物本来应该是中性的，因为人的观念介入，而有了高下，有了分别</p>
</blockquote>
<p>“未始有封也”，并没有界线。</p>
<blockquote>
<p>阮云：物于物之间不存在界限，一直在转化</p>
</blockquote>
<p>物质文明越发达，社会越复杂，思想之混乱，是非善恶观念之复杂，都是障道的因缘</p>
<blockquote>
<p>阮云：这些就是五蕴，色受想行识。</p>
</blockquote>
<p>我再提一下全篇的宗旨，道体，宇宙万有的本体，本来是绝对、同一的。当道体起作用的时候，一切万类的现象不同，作用不同，但道体是一样的。</p>
<blockquote>
<p>阮云：一以贯之的就是道体吧</p>
</blockquote>
<p>道体是“一”的。因为大家自己的观念不同，被现象骗了，所以各家有各家的看法，儒家有儒家的看法，墨家有墨家的看法 道家有道家的看法，各种说法都不同，应用的方法也不同。因此，被现象迷住了，忘记了本来。</p>
<p>人要晓得“知几”，把握自己生命的重点，不“知几”，对于自己是在开玩笑，没有用</p>
<blockquote>
<p>阮云：几通机，因为物化，事物总在变动之中，所以要知晓关键时机</p>
</blockquote>
<p>不管是宗教的，哲学的、科学的、古代诸子百家的，现在的科学分门别类，都有一个大原则，一切学问与人身心性命没有关系的，它不会成立不会存在。</p>
<p>天地是与我同存的，万物是与我同一的，我们跟万物同样都是那个东西的一份子，并非天地就是我，也不是我就是天地，物是物，我是我，天还是天，地还是地。</p>
<p>佛学里的有一句名言“如人饮水，冷暖自知”，就是“圣人怀之”，到了那个程度，那个境界，“圣人”只有自己知道</p>
<p>“是非以不辩为解脱”，禅宗注重的行为，不完全是打坐，所以百丈禅师讲“疾病以减食为汤药”，有了病最好少吃东西，肠胃清理一下，</p>
<p>上古我们的老祖宗是吃狗肉的，现在广东人保持了这一习俗，上古祭祖宗都要用狗肉来祭，大约到了商、周以后，在祭祀中，才渐渐免除了狗肉这项祭品，但在某些祀典中，仍然须用草扎一个象形的狗，替代一头真的狗，这就是刍狗的来源。</p>
<blockquote>
<p>阮云：以万物为刍狗，把万物当成草扎的狗，在祭祀的时候，很庄重，把草狗当成真狗，当祭祀完，就可以扔掉了。</p>
</blockquote>
<p>“大廉不嗛”的道理，我经常说一个笑话，拿什么来比呢？拿猪来比，实际世界上最爱干净的是猪，研究生物学的都懂。你看猪一天到晚用嘴东拱西拱，人们以为猪脏，其实它最爱清洁了，脏东西一点都看不惯，看到脏东西就把它拱开，结果是越拱越脏。</p>
<blockquote>
<p>阮云：嗛通谦，真正的大廉没有谦让，并不是不谈钱。</p>
</blockquote>
<p>啮缺问：你知不知道，天地万物有一个到了最高处基本是相同的，绝对的，同一的那个东西？王倪答复：我哪里知道？换一句话说，我不知道。啮缺又问：你为什么不知道？你知不知道你那个时候你不知道的？王倪说我也不知道，我也不懂。那么啮缺就问：既然这样，宇宙万物的最高处是无知吗？王倪又说，那我也不知道。我们中国文化有一个成语，叫“一问三不知”</p>
<p>“吊诡”就是佛家禅宗所谓“机锋”。中国学武的有一句话：“弓在弦上，不得不发”，弓拉满了，箭在弦上，不得不发，这是“机”。彼此两个机关相对，非常锋利，很快，不可以用思想，来不及用思想</p>
<blockquote>
<p>阮云：所谓势，物理中的势能就是来自此处</p>
</blockquote>
<p>“不可思议”，最高的真理就这四个字。不可以用思想知识去推测，不可用逻辑思辩来断定。</p>
<p>知其不可奈何而安之若命，德之至也。</p>
<p>凡交近则必相靡以信，远则必忠之以言。言必或传之。</p>
<p>夫两喜必多溢美之言，两怒必多溢恶之言。凡溢之类妄，妄则其信之也莫，莫则传言者殃。</p>
<blockquote>
<p>阮云：所以君子之交淡如水</p>
</blockquote>
<p>克核太至，则必有不肖之心应之，而不知其然也。</p>
<p>我们常常看到办事的，做公务员的“迁令”。譬如我发现有跟我做事的同学，我说：“请你帮我把下面那一本书拿上来。”结果他到了下面对另一人说，“某某人，老师叫你把那本书拿上去。”这就叫“迁令”，已经不对了。做人要“不迁令”。</p>
<blockquote>
<p>阮云：然而现在企业在中层都是这种人，可笑可笑</p>
</blockquote>
<p>“美成在久，”就是我们平时所讲的，好事不在忙。成就好的事情，不是一时做得到的。坏的事情却容易成就，一成就了以往，来不及改正。所以作人处事要慎重地考虑。</p>
<p>“有人于此，其德天杀。与之为无方，则危吾国，与之为有方，则危吾身。</p>
<blockquote>
<p>阮云：与暴君相处，如果任由放纵他，危害国家，如果约束他，危害自身性命</p>
</blockquote>
<p>形就而入，且为颠为灭，为崩为蹶；心和而出，且为声为名，为妖为孽。</p>
<blockquote>
<p>阮云：讲得是要和光同尘，如果顺从本心，反而被认为妖孽</p>
</blockquote>
<p>汝不知夫螳螂乎？怒其臂以当车辙，不知其不胜任也，是其才之美者也。戒之，慎之，积伐而美者以犯之，几矣！</p>
<p>故未终其天年而中道之夭于斧斤，此材之患也。</p>
<p>支离其德</p>
<blockquote>
<p>阮云：支离是古人叫支离疏，长得奇形怪状的，然而却免遭各种劳役。这里讲的还是无用之用</p>
</blockquote>
<p>福轻乎羽，莫之知载。</p>
<blockquote>
<p>阮云：幸福这东西比羽毛还轻，不知道拿什么把它装起来。</p>
</blockquote>
<p>《人间世》全篇的宗旨：“世路难行”。并不是世路是不可行的，是可行的。人生要你自己善于处。那么归结起来告诉我们什么东西呢？三个字：守本份。人要守本份，在什么立场就做什么事，处什么态度。大家进了歌厅就要跟着唱歌，进了舞厅就要跟着跳舞，大家喝醉了你就要装醉，大家清醒起来你也要跟着清醒，大家都在做工你却在睡觉，那就不是疯而是蠢到极点了。</p>
<p>我们知道，春秋战国的文化，道跟德是分开的，道是体，就是内涵，是每个人修养学问的内涵；德是用，得了道体就能起用，即用世之道。</p>
<p>自其异者视之，肝胆楚越也；自其同者视之，万物皆一也。</p>
<p>吾所谓无情者，言人之不以好恶内伤其身，常因自然而不益生也。</p>
<p>尤其这一篇我们要了解什么是“命”，这个命不是算八字那个命，它在哲学的理论上叫天命，在实际的修证就是认清生命的来源。</p>
<p>这个“命”相当于佛学中讲的业，善的是善业，恶的是恶业，不善不恶的是无记业。</p>
<p>古书上的“天”字，大约概括了五类内涵：（一）天文学上物理世界的天体之天，如《周易》乾卦卦辞“天行健”的“天”。 （二）具有宗教色彩，信仰上的主宰之天，如《左传》所说的“昊天不吊”。（三）理性上的天，如《诗经》小节的“苍天苍天”。（四）心理性情上的天，如《泰誓》和《孟子》的“天视自我民视，天听自我民听”。（五）形而上的天，如《中庸》所谓“天命之谓性”。</p>
<p>那么这个道怎么来的呢？两个路线：一是抛弃了你的小聪明，而求那个“无知之知”的大道；另一个路线，把世间的聪明学问都通到了极点，最后归到“一无所知而无不知”，也就得道了。</p>
<blockquote>
<p>阮云：和禅宗所谓顿悟和渐修一个意思</p>
</blockquote>
<p>不要弄得像现在大学的史学系一样，自己好像比历史还高明，然后去分析历史批判历史，结果你不是历史，你是书呆子。现在研究历史同我们过去不同，我们过去研究历史，是使自己懂得如何作人做事，现在不然，现在是比历史都还要高。</p>
<blockquote>
<p>阮云：我们的历史课本常犯这种毛病，脱离时代来谈局限性</p>
</blockquote>
<p>“未死先学死，有生即杀生”，“生”就是心念一动，就要把心念通通去掉；这个“死”，不是自己吃安眠药去死，是要烦恼杂念妄想通通死光，就是杀的作用。也就是说，心中的烦恼杂念通通死光，生命的本能才会恢复，才会长生不死。</p>
<p>孔子讲得很彻底：“徒善不足以为敬，徒法不足以自刑”。</p>
<p>汉家自有制度，本以霸王道杂之</p>
<p>泉涸，鱼相与处于陆，相呴以湿，相濡以沫，不如相忘于江湖。与其誉尧而非桀也，不如两忘而化其道。</p>
<p>杀生者不死，生生者不生。其为物，无不将也，无不迎也，无不毁也，无不成也，其名为撄宁。撄宁也者，撄而后成者也。</p>
<p>孔子儒家所标榜的圣王之道，得了道才可以入世，“终日挥形”，他们虽然一天到晚看起来忙死了，但“神气无变”，内在修养神与气，并没有受忙碌的外界所影响。</p>
<p>“各有前因莫羡人”，每一个人都有各自的前因后果，你不要嫉妒羡慕人家。这些都是人生哲学的问题。</p>
<p>认为时代是进步的，这是站在物质文明立场上来讲。今后的人在物质的享受上，比我们现在还要进步，最后的形态，是物质文明一切一切都在进步；认为时代是退化退步的，这是站在精神文明来讲，这两种观念，必须要推论到宗教上面去。</p>
<p>你看其它国家的人，标榜人道，可见很不人道，所以才需要人道。</p>
<p>“其卧徐徐，其觉于于”，这两句话代表佛学禅宗讲的“梦觉一如”，人没有昏迷过，无所谓睡眠，睡眠也是清醒，醒了以后，也没有昏迷过，在清醒中“人生如梦”，本来是梦境，这没有什么两样。</p>
<blockquote>
<p>阮云：所谓色即是空，空即是色。</p>
</blockquote>
<p>“以己出经”，拿自己推理别人，就是儒家讲的推己及人的忠恕之道。</p>
<p>圣帝明王，就是动物园的园长，就养一些高明的动物。</p>
<blockquote>
<p>阮云：所以领导者并不是自己多强，是要能招到厉害的人，管好厉害的人。</p>
</blockquote>
<p>这个道理只可以悟不可以讲，讲出来就很讨厌的。</p>
<blockquote>
<p>阮云：就如鲁迅说的，“这人终究会死的”，虽然是实话，但是难听</p>
</blockquote>
<p>没有一个人会在妈妈肚子里问：我为什么要生出来？我生出来的目的是什么？没有一个人是问明白了才生出来的。所以人生就以人生为目的，本来如此，这个题目本身就是答案，还有什么好讲的！</p>
<p>我们人生只有十二个字：“看得破，忍不过；想得到，做不来。”</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>哲学</tag>
      </tags>
  </entry>
  <entry>
    <title>我们为什么不能理解别人</title>
    <url>/2016/06/12/%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E7%90%86%E8%A7%A3%E5%88%AB%E4%BA%BA/</url>
    <content><![CDATA[<p>　　这世界上这么多纷争与烦恼，很大一部分都是因为人与人之间不能相互理解，他人为敌国、以邻为壑等等层出不穷。之所以不能理解，是因为<strong>同一个东西，对于不同的人（或者不同时间或空间的同一个人），价值是全然不同的。</strong></p>
<p>　　可能你认为重于生命的东西，于他人不过徒增笑谈尔。比如信教者的斋戒等行为，对无神论者来说是多余的，而对信徒来说却是可能比生命更重要。<br>　　<br>　　又如，你还在大学的时候，这时的时间对你来说十分廉价，所以你愿意去发8元每小时的宣传单；等你事业有成或是垂垂老矣，你还愿意花这一小时去干这个吗，也许只会感叹自己当时太年轻吧。一个人尚且不能理解年轻时的自己，又怎能奢望他理解别人呢？<br>　　<br>　　真正的相互理解是要认同他人对事物价值的判定，但一个事物对他们的价值有天壤之别，这样你叫他们如何相互理解呢？</p>
<p>　　那么怎么解决这种情况呢？也就是怎么寻找一种相对普适的价值衡量方法呢？<br>　　<br>　　我的一个浅薄的思路是，<strong>衡量一个事物的价值可以通过这个人的付出相对自身的稀缺性，这种付出包括物资的付出和精神上承受的压力。</strong><br>　　<br>　　比如说，一个乞丐付出了仅有的100元买了个表，和一个百万富豪付出1000元买了个表。对乞丐来说这表是全部，而对富豪来说表是1&#x2F;1000。因此乞丐对表爱护有加，富豪对表弃之如弊履，就完全可以理解了。也可以说乞丐的表价值大于富豪的表。<br>　　<br>　　虽然绝对价值不可判定，并且造成很多纷争。但是，同一件事对不同的人价值不同也不是全然没有好处，至少在市场经济里，你们完全可以交换这些东西嘛。</p>
<p>　　要之，于娑婆世界中，众生皆苦，<strong>要以事物相对他人的稀缺性来看他人的付出与承受，如此理解他人才成为可能。</strong></p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>世界观</tag>
      </tags>
  </entry>
  <entry>
    <title>无常迅速</title>
    <url>/2017/10/17/%E6%97%A0%E5%B8%B8%E8%BF%85%E9%80%9F/</url>
    <content><![CDATA[<p>昨夜刚收到外祖父离世的消息, 顿时心里空落落的, 不知何处安放.</p>
<p>随着年岁渐长, 看着亲人离自己远去, 真是很难接受, 恨自己的无力, 不能为他们做些什么.</p>
<p>或许这就是佛家说的求不得吧, 不喜欢的事物它还是会来, 喜欢的事物也还是会走, 来或是不来都难以求得, 父亲那是也是这样.</p>
<p>死生事大, 无常迅速.</p>
<p>无法, 唯有精进.</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
      </tags>
  </entry>
  <entry>
    <title>每个人心中都有个小王子</title>
    <url>/2015/10/18/%E6%AF%8F%E4%B8%AA%E4%BA%BA%E5%BF%83%E4%B8%AD%E9%83%BD%E6%9C%89%E4%B8%AA%E5%B0%8F%E7%8E%8B%E5%AD%90/</url>
    <content><![CDATA[<p><img data-src="https://image.ponder.work/mweb/2019-12-01-15752095909508.jpg"><br>　　下午去看了《小王子》电影，拍得很好，在忠实原著的基础上又有所发展。回来又把原著给温习了一遍，感触颇深。<br>　　<br>　　记得李安在拍《卧虎藏龙》的时候说过<code>每个人心中都有把青冥剑</code>，而我想说<code>每个人心中都有个小王子</code>。<br>　　作者说过，这是<code>写给曾经是孩子的成人的童话</code>。所以，不论文章还是电影，都比较有深度，适合我们这些曾经是孩子的人观看。</p>
<span id="more"></span>
<p>　　开篇，小女孩女主上学面试不力，为了能上名校，母亲只能搬家到学校附近的学区房里，就这样小女孩和原著里的飞行员命运般的相遇了。此时飞行员已经年老，却依然想念当年的小王子，并把这个故事传递给女主，引发了女主之后去寻找小王子的故事。</p>
<p>　　关于电影情节就介绍到这里，不然就有剧透之嫌。下面要讲的主要还是对小王子原本故事的解读，可以放心阅读。</p>
<p>　　所谓文学作品就是作者的梦，里面是各种元素的杂糅，各个人物和情节都有现实的影子，但有不等同现实。文中，飞行员，毫无疑问代表作者；小王子，就比较复杂了，姑且可以认为是<code>童真</code>，并且融入了一些作者珍视的东西，比如对玫瑰的爱；玫瑰，作者的爱人，也是让作者有所愧疚的人。小王子因为和玫瑰闹了别扭，离开他居住的那房子般大小的小行星，路上经过了几颗325、326、等等小行星，每颗小行星上都居住了一个大人——滑稽的大人，小王子与他们交谈后来到了地球。</p>
<p>　　其中酒鬼所说的话极具代表性。</p>
<blockquote>
<p>小王子：你为什么要喝酒？<br>酒鬼：为了忘记羞愧的事。<br>小王子：羞愧什么呢？<br>酒鬼：喝酒。</p>
</blockquote>
<p>　　我们这些大人很多时候就是这么奇怪，好像自己每天做的东西都是理所应当，‘我们都是正经人，都做正经事。’，却离真正想要的东西越来越远。</p>
<p>　　小王子就是小时候的我们，小孩子眼中的世界和小王子的星球一样，虽然小但是有他喜爱的东西就够了。而这些大人，每人忙忙碌碌，追求着自己不需要的东西。</p>
<p>　　<strong>每个人心中都有个小王子，那个我们曾经拥有，却终将逝去的小王子。</strong></p>
<h2 id="书摘"><a href="#书摘" class="headerlink" title="书摘"></a>书摘</h2><ul>
<li><p>年轻的时候如果不好好去爱，等到老去的时候就一定会因为曾经的轻狂而感到后悔。</p>
</li>
<li><p>他的脸色气得发红，接着说道：“若有人爱上了开在亿万星星上的一株花，那么看着星星就足以使他感到幸福。”</p>
</li>
<li><p>我本该猜出她那站不住脚的花招后面的温情。花儿是多么自相矛盾！可我太小，不懂得如何去爱她……”</p>
</li>
<li><p>“人都在什么地方？”小王子终于又开了口，“沙漠里真有点孤独。” “在人群里照样孤独。”蛇说</p>
</li>
<li><p>狐狸说，“人类是不再有空去了解任何东西的，他们到商店去购买现成的东西”。</p>
</li>
<li><p>只有用心才能看到本质的东西，精髓是眼睛看不见的。</p>
</li>
<li><p>“只有孩子知道自己在找什么，”小王子说，“他们为一个布娃娃花费时间，布娃娃就变得很重要，如果布娃娃被夺走，他们就哭泣……”“他们真幸运。”</p>
</li>
<li><p>“你这里的人在同一个花园种植 5000朵玫瑰……”小王子说，“却不能从中找到自己要找的东西……”</p>
</li>
<li><p>“有一天，我看见过四十三次日落。”<br>“你知道，当人们感到非常苦闷时，总是喜欢日落的。”<br>“一天四十三次，你怎么会这么苦闷？”<br>小王子没有回答。</p>
</li>
</ul>
<!--![](http://img-storage.qiniudn.com/15-10-18/2653650.jpg)-->


]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>影评</tag>
      </tags>
  </entry>
  <entry>
    <title>沟通</title>
    <url>/2016/06/26/%E6%B2%9F%E9%80%9A/</url>
    <content><![CDATA[<blockquote>
<p>no man is a island.</p>
</blockquote>
<p>　　西谚有云“没有人是个孤岛”，人是社会性动物，很多自我价值都在社交中实现，自是离不开沟通。</p>
<p>　　但真正的沟通又是何其难？某种意义上，你永远都不能知道对方真正的意思。当然，有时你连自己是什么意思都不甚了解。</p>
<p>　　在聊天中经常出现的是，表面上相谈甚欢，背地里很可能是呵呵一笑。毕竟不同价值观，你不能奢望别人能完全理解你，不然怎么老说知音难觅呢？</p>
<p>　　叔本华曾说</p>
<blockquote>
<p>人就像寒冬里的刺猬,互相靠得太近,会觉得刺痛;彼此离得太远,却又会感觉寒冷.</p>
</blockquote>
<p>　　大师所言简直入木三分，而且这些刺猬靠得太近的时候，会说“这个距离太热了”，而不是“你刺到我了”。人与人之间的沟通乃至交往，正是寻找这个合适的距离，这个对双方都合适的距离。</p>
<p>　　人人都有刺，你的刺一方面是你防卫的武器，另一方面阻止距离更近一步的枷锁。没安全感的人，他的刺就特别长，这样虽然安全了，但别人永远无法接近你。所以我们往往躲在自己厚重的防卫之下，同时感叹着“怎么没有真正理解我的人呢？”。</p>
<p>　　但其实这些刺都是无用的累赘，你以为它能保护你，其实它把所有人都隔离在外。其实能伤害你的只有你自己，敞开你的心扉吧，你会找到真正的知音，就算不幸有人要辱骂你伤害你，你不放在心上，他就无的放矢。</p>
<p>　　放下无谓的防备，追求无碍的沟通。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
      </tags>
  </entry>
  <entry>
    <title>生死之间有大恐怖</title>
    <url>/2015/10/10/%E7%94%9F%E6%AD%BB%E4%B9%8B%E9%97%B4%E6%9C%89%E5%A4%A7%E6%81%90%E6%80%96/</url>
    <content><![CDATA[<p>　　昨天晚上听到一则消息，小学一老师得骨癌，37岁，自杀了。<br>　　<br>　　不知怎的，心里乱乱的，就想起“生死之间有大恐怖”这句话来了。<br>　　<br>　　人生在世确如白驹过隙，忽然而已。而这白驹过隙却也还有快慢之分，如何不令人怅然。犹记得，《兰亭集序》里一句“修短随化，终期于尽。”，少时懵懂只觉语句优美，终不解其中深意。到如今，自己也算是经历了一些，才明白这寥寥数语却是道尽世间沧桑。个体寿命的长与短，都是由造化决定，最终都不免走向那个尽头。然而这个中差别，却让多少世人肝肠寸断。</p>
<p>　　也曾想，自己的生命有多长，然后就是一阵惊惧。<br>　　<br>　　生命实在是太过短暂，而且很可能在你不经意时终结。</p>
<blockquote>
<p>生死事大，无常迅速。</p>
</blockquote>
<p>　　唉！无法，徒增忧伤而已。</p>
<blockquote>
<p>人生苦短，及时行乐。<br>有愿立偿，不余遗憾。</p>
</blockquote>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>世界观</tag>
      </tags>
  </entry>
  <entry>
    <title>番茄工作法工具推荐</title>
    <url>/2016/02/11/%E7%95%AA%E8%8C%84%E5%B7%A5%E4%BD%9C%E6%B3%95%E5%B7%A5%E5%85%B7%E6%8E%A8%E8%8D%90/</url>
    <content><![CDATA[<h2 id="番茄工作法简介"><a href="#番茄工作法简介" class="headerlink" title="番茄工作法简介"></a>番茄工作法简介</h2><p>番茄工作法（英语：Pomodoro Technique）是一种时间管理法方法，在上世纪八十年代由Francesco Cirillo创立。 该方法使用一个定时器来分割出一个一般为25分钟的工作时间和5分钟的休息时间，而那些时间段被称为pomodori，为意大利语单词 pomodoro（中文：番茄）之复数。</p>
<p>番茄工作法有五个基本步骤：</p>
<ol>
<li>决定待完成的任务</li>
<li>设定番茄工作法定时器至 n 分钟（通常为25分钟）。</li>
<li>持续工作直至定时器提示,记下一个x。</li>
<li>短暂休息3-5分钟。</li>
<li>每四个x，休息15-30分钟。</li>
</ol>
<p>番茄工作法的关键是规划，追踪，记录，处理，以及可视化。在规划阶段，任务被根据优先级排入”To Do Today” list。 这允许用户预计每个任务的工作量。当每个番茄时结束后，成果会被记录下来以提高参与者的成就感并为未来的自我观察和改进提供原始数据。</p>
<p>番茄工作法一般与<a href="http://baike.baidu.com/link?url=ZlEFP9gdzCBmRrqAGqClmdyQ3_1t4Op33-tTyXbN_Gw3LRRqU3zmpwowPhW8VSKLriuLGsq8gJD-TJBO40ez_q"><code>GTD</code></a>相结合，能取得事半功倍的效果。</p>
<h2 id="工具推荐"><a href="#工具推荐" class="headerlink" title="工具推荐"></a>工具推荐</h2><ol>
<li>GTD软件，用于任务规划和记录，推荐<a href="https://www.dida365.com/"><code>滴答清单</code></a>或<a href="http://doitim.com/cn/"><code>Doit.im</code></a></li>
<li>小米手环，震动提醒防止打扰别人。</li>
<li>米环工具 Mi Band Tools, 百度网盘: <a href="https://pan.baidu.com/s/1qZ54Rr2">https://pan.baidu.com/s/1qZ54Rr2</a> 密码: pcqh</li>
<li>极简番茄, 百度网盘: <a href="https://pan.baidu.com/s/1smZUZxj">https://pan.baidu.com/s/1smZUZxj</a> 密码: 8fff</li>
</ol>
<p>小米手环本来没有番茄钟的功能，通过<code>Mi Band Tools</code>和<code>极简番茄</code>这两个应用，在米环工具的“应用”中添加 极简番茄，可以实现手环震动提醒番茄钟。<br>当然你也可以直接使用<code>极简番茄</code>，通过手机震动或是响铃提醒，或者买一个计时器。</p>
<h3 id="小米手环和极简番茄结合方法"><a href="#小米手环和极简番茄结合方法" class="headerlink" title="小米手环和极简番茄结合方法"></a>小米手环和极简番茄结合方法</h3><p>在米环工具的“应用”中添加 极简番茄。</p>
<p>具体的应用配置可以参考截图，一般只修改震动次数、忽略不可清除的通知，其他默认即可</p>
<p><img data-src="https://image.ponder.work/mweb/2020-03-05-15833917724588.jpg"><br><img data-src="https://image.ponder.work/mweb/2020-03-05-15833917804538.jpg"><br><img data-src="https://image.ponder.work/mweb/2020-03-05-15833917911022.jpg"></p>
]]></content>
      <categories>
        <category>工作生活</category>
      </categories>
      <tags>
        <tag>GTD</tag>
        <tag>番茄工作法</tag>
      </tags>
  </entry>
  <entry>
    <title>简明双标指南</title>
    <url>/2020/06/13/%E7%AE%80%E6%98%8E%E5%8F%8C%E6%A0%87%E6%8C%87%E5%8D%97/</url>
    <content><![CDATA[<p>最近一段时期有很多事情发生，也颠覆了我们的认知（如I cannot breathe），同时也发现了很多双标事件。</p>
<p>下面就简单地总结下，如何双标。</p>
<p>双标的核心是，定性的看问题，不要就事论事具体分析。</p>
<p>双标的要点之一，选取部分真相进行概括定性。<br>这里的选取非常关键，我们应该选择性地看见我们想看见的东西。假如B捡了地上的一块钱并据为己有，贪小便宜不是君子所为，这人肯定是品行不端。假如A也这么干了，对不起我没有看见。假如A捡了垃圾放垃圾桶了，这时我们就可以说，从一件微不足道的事情上就可以看出A真是道德高尚。</p>
<p>双标的要点之二，定性之后的上纲上线。<br>由于第一步我们已经定性了A和B，以后再发生事情就很好处理了。假如都做了好事，A就是好人性质的体现，B因为他是坏人肯定是另有目的的，是伪装的。假如都做了坏事，反过来处理就好了。比如，你认定A是好人，B是坏人，假设他们都随地吐痰了。这时就可以说A肯定是不小心的，大善人怎么可能做这种事；可以说B真是狗改不了吃屎，连基本的公德心都没有。假设都做了好事，扶老奶奶过马路，A就是一以贯之的道德楷模，值得学习；B肯定是装的，为了方便自己做坏事。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
      </tags>
  </entry>
  <entry>
    <title>《胡适口述自传》读书笔记</title>
    <url>/2020/03/22/%E8%83%A1%E9%80%82%E5%8F%A3%E8%BF%B0%E8%87%AA%E4%BC%A0-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<p>杜威有系统的思想, 五个阶段:</p>
<ol>
<li>前奏: 困惑, 疑虑</li>
<li>决定困惑疑虑在何处</li>
<li>假设解决方法</li>
<li>选择方法其一</li>
<li>证明假设</li>
</ol>
<p>实用主义, 实为 “实验主义”</p>
<p>苏格拉底: 天下任何事物和概念都有其 “普遍界说”, 比如说，猫的“普遍界说”就是“捉老鼠”。</p>
<p>胡适有好疑问的信条, 唯有对这一信条不疑问</p>
<p>诸公茶余溺后, 伸缩乎竹椅之上, 打桥牌则 “金刚钻”, “克鲁伯”, 纸声飕飕, 下象棋则过河卒子, 拼命向前, 无牌无棋, 则张家山前, 李家山后, 饮食男女, 政治时事, 粪土当朝万户侯! 乖乖, 真是身在茶馆, 心有邦国, 眼观世界, 牛皮无边</p>
<blockquote>
<p>阮云: 怎一个颓废了得</p>
</blockquote>
<p>我们政学两界都害了过分依赖权威的毛病.</p>
<blockquote>
<p>阮云: 让人个个人又想成为权威</p>
</blockquote>
<blockquote>
<p>阮云: 革命一定要年轻而冲动, 你想革他人之命, 须先将已命悬于腰间, 故革命言论一定要偏激而富有煽动性. 过于理性的人做不出革命之举(后又读吴思的书, 又有些不同看法)</p>
</blockquote>
<p>朱熹: “格物”是, “格”, 至也, “物”, 犹事也.</p>
]]></content>
      <categories>
        <category>阅读</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
      </tags>
  </entry>
  <entry>
    <title>自学的陷阱</title>
    <url>/2020/05/17/%E8%87%AA%E5%AD%A6%E7%9A%84%E9%99%B7%E9%98%B1/</url>
    <content><![CDATA[<p>自己常年都在自学，在自学的路上走了很多弯路，到最后发现很多时候走的都是最艰难的或者是错误的道路，抑或是一直在尝试各种道路，从未选择一条坚持走下去。</p>
<span id="more"></span>

<p>举最熟悉的编程来说，它应该是最适合自学的领域了。网络上相关的学习资源也数不胜数，但是为何很多人最后进步很慢或者是干脆“从入门到放弃了”，究其原因大抵是走了错误的道路。</p>
<p>打开知乎，类似“如何学习编程”的问题数不胜数，也有很多人善意地给了自己的回答。记得我当年也问过类似的问题，有人说先从SICP入手吧，有人说直接LeetCode刷题，有人说学C，有人说学Python。或许一千个人就有一千种答案，但是到底哪个能帮助到你呢？每个人的答案，其实都是他过往经验的总结。</p>
<p>由于每个人的资质、禀赋、基础（下面统称为资质）并不相同，每个方法也有其适应人群和局限，照搬别人的路子只能是失败。越高资质的人（利根人），对于各种方法的兼容性越好，当然效果是有区别的。低资质的人（钝根人），越发体现方法的重要性。这里的方法就是学习的路径，佛家称之为法门。一切法门都是手段而已，都是为了最终能学到东西。</p>
<p>譬如爬山，有盘山公路，也有直接的楼梯，也有索道，并无绝对高下之分，只有合适与不合适。<br>体力不同能承受的坡度就不同，超出限度最后只能半途而废，所以得从这些路径上选择自己恰好能接受的那条路。当自学的时候，由于没有老师，我们很容易步入错误的道路，从而落入陷阱。</p>
<p>那么，如何适合自己的道路呢？首先要了解自己的的优劣势，明确目标，然后了解每条路径的特点（要求、效率、产出等）。<br>具体来说，逐条尝试路径，评估投入产出，做记录，并对比当前的目标（为了欣赏风景，还是锻炼身体），选择相对合适的路径。而且选择不应该是僵化的，应该持续地评估，因为的你会变、路会变、你和路之间还会变。当然不要去尝试所有的路径，一则时间精力有限，二则我们也不是路径测评师。</p>
<p>有朝一日学有所得，能够为人导师时，记得随人讲法。</p>
<p>最后，学习使我快乐。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>学习方法</tag>
      </tags>
  </entry>
  <entry>
    <title>致己</title>
    <url>/2016/06/16/%E8%87%B4%E5%B7%B1/</url>
    <content><![CDATA[<p>　　吾实乃庸人，于理虽觉明晰通达，于情却犹豫不畅。</p>
<p>　　独处之时意淫幻想，骤然临之慌乱畏惧不前。平生如遇佳人则豫惧无措，欲与之亲近而不得。而后偶一回想，怅然颓首而无所解。如此于人于己有何益处，空留念想，只招惆怅而已。</p>
<p>　　呜呼，面皮之为物当得了几两干饭，胆气之所缺非得几斤豹胆所能解。</p>
<p>　　为文以纪，聊为自勉，愿余能一扫前非。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>世界观</tag>
      </tags>
  </entry>
  <entry>
    <title>补记杂感一则</title>
    <url>/2016/06/16/%E8%A1%A5%E8%AE%B0%E6%9D%82%E6%84%9F%E4%B8%80%E5%88%99/</url>
    <content><![CDATA[<p>　　人事皆非己见，人心不堪洞见。</p>
<p>　　既不若向之所求，何须用心添烦恼。</p>
<p>　　不谏已悟，来者可追。</p>
<p>　　当效拂袖，心役即脱。</p>
]]></content>
      <categories>
        <category>随笔</category>
      </categories>
      <tags>
        <tag>随笔</tag>
        <tag>世界观</tag>
      </tags>
  </entry>
  <entry>
    <title>通过适配器模式优化Python代码</title>
    <url>/2016/06/19/%E9%80%9A%E8%BF%87%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F%E4%BC%98%E5%8C%96Python%E4%BB%A3%E7%A0%81/</url>
    <content><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>　　当你维护旧项目的时候，可能要优化一些原有的代码，又不想大改特改。毕竟设计模式的原则是对更改关闭，对扩展开放。假如大改特改势必会引入新的bug，增加测试的工作量。优化代码的目的是提高稳定性和效率，如果引入了新bug就得不偿失了。</p>
<p>　　这时我们<strong>可使用适配器或装饰器，在不改变调用的情况下，优化代码。</strong></p>
<span id="more"></span>

<h2 id="举个栗子"><a href="#举个栗子" class="headerlink" title="举个栗子"></a>举个栗子</h2><p>　　比如下面这代码段，封装了一个MySQL的连接，但是<strong>只返回了游标对象（cursor），没有返回连接对象（connnection）</strong>。这样每次调用完这个函数之后，<strong>数据库连接不能关闭</strong>，很可能会导致连接量到达上限，引起性能问题。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> MySQLdb</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mysql_connect</span>():</span><br><span class="line">    conn = MySQLdb.connect(host=<span class="string">&#x27;localhost&#x27;</span>,</span><br><span class="line">                         port=<span class="number">3306</span>,</span><br><span class="line">                         user=<span class="string">&#x27;root&#x27;</span>,</span><br><span class="line">                         passwd=<span class="string">&#x27;123456&#x27;</span>,</span><br><span class="line">                         db=<span class="string">&#x27;db&#x27;</span>,</span><br><span class="line">                         charset=<span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">                         use_unicode=<span class="literal">False</span>,</span><br><span class="line">                         unix_socket=<span class="string">&#x27;/tmp/mysql3306.sock&#x27;</span>)</span><br><span class="line">    cur = conn.cursor()</span><br><span class="line">    <span class="keyword">return</span> cur</span><br></pre></td></tr></table></figure>


<h2 id="使用适配器优化"><a href="#使用适配器优化" class="headerlink" title="使用适配器优化"></a>使用适配器优化</h2><p>　　所以我写了一个适配器类，<strong>通过适配器对象包装游标和连接</strong>，并且使<code>mysql_connect</code>函数返回适配器对象，以解决连接不能关闭的问题。<br>　　<code>MySQLConnectAdapter</code>适配器组合了<code>cursor</code>和<code>connnection</code>，**调用<code>close</code>方法的时候会同时关闭<code>cursor</code>和<code>connnection</code>。**重写<code>__getattribute__</code>方法，将除<code>close</code>之外的属性和方法重定向的<code>self._cur</code>对象上，实现对原有调用的兼容。需要注意，<strong>一旦重写<code>__getattribute__</code>方法，所以属性和方法查找都会通过自定义的<code>__getattribute__</code>，注意防止无穷递归。</strong></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> MySQLdb</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MySQLConnectAdapter</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, conn, cur</span>):</span><br><span class="line">        <span class="variable language_">self</span>._conn = conn</span><br><span class="line">        <span class="variable language_">self</span>._cur = cur</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__getattribute__</span>(<span class="params">self, name</span>):</span><br><span class="line">        <span class="keyword">if</span> name <span class="keyword">in</span> (<span class="string">&#x27;close&#x27;</span>, <span class="string">&#x27;_conn&#x27;</span>, <span class="string">&#x27;_cur&#x27;</span>):</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">object</span>.__getattribute__(<span class="variable language_">self</span>, name)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">object</span>.__getattribute__(<span class="variable language_">self</span>, <span class="string">&#x27;_cur&#x27;</span>).__getattribute__(name)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">close</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>._cur.close()</span><br><span class="line">        <span class="variable language_">self</span>._conn.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mysql_connect</span>():</span><br><span class="line">    conn = MySQLdb.connect(host=<span class="string">&#x27;localhost&#x27;</span>,</span><br><span class="line">                         port=<span class="number">3306</span>,</span><br><span class="line">                         user=<span class="string">&#x27;root&#x27;</span>,</span><br><span class="line">                         passwd=<span class="string">&#x27;123456&#x27;</span>,</span><br><span class="line">                         db=<span class="string">&#x27;db&#x27;</span>,</span><br><span class="line">                         charset=<span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">                         use_unicode=<span class="literal">False</span>,</span><br><span class="line">                         unix_socket=<span class="string">&#x27;/tmp/mysql3306.sock&#x27;</span>)</span><br><span class="line">    cur = conn.cursor()</span><br><span class="line">    safe_cur = MySQLConnectAdapter(conn, cur)</span><br><span class="line">    <span class="keyword">return</span> safe_cur</span><br></pre></td></tr></table></figure>]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>Python</tag>
      </tags>
  </entry>
  <entry>
    <title>学习重构：代码的坏味道</title>
    <url>/2020/03/21/%E9%87%8D%E6%9E%84%EF%BC%9A%E4%BB%A3%E7%A0%81%E7%9A%84%E5%9D%8F%E5%91%B3%E9%81%93/</url>
    <content><![CDATA[<p>读完了<a href="https://book.douban.com/subject/30468597/">重构</a>，感觉受益匪浅，准备写一个系列的文章。</p>
<p>这篇讲代码的坏味道，学习它是为了提升我们的眼力，为找到对应的重构方法做准备</p>
<span id="more"></span>


<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><h3 id="缺少封装"><a href="#缺少封装" class="headerlink" title="缺少封装"></a>缺少封装</h3><h4 id="全局数据（Global-Data）"><a href="#全局数据（Global-Data）" class="headerlink" title="全局数据（Global Data）"></a>全局数据（Global Data）</h4><p>症状: 全局作用域的变量, 类变量, 单例</p>
<p>分析: 从代码库的任何一个角落都可以修改它，而且没有任何机制可以探测出到底哪段代码做出了修改。最刺鼻的坏味道之一。</p>
<h4 id="可变数据（Mutable-Data）"><a href="#可变数据（Mutable-Data）" class="headerlink" title="可变数据（Mutable Data）"></a>可变数据（Mutable Data）</h4><p>症状: 将可变对象作为函数参数</p>
<p>分析: 难以追踪数据变更, 定位故障</p>
<h4 id="过长参数列表（Long-Parameter-List）"><a href="#过长参数列表（Long-Parameter-List）" class="headerlink" title="过长参数列表（Long Parameter List）"></a>过长参数列表（Long Parameter List）</h4><p>症状: 函数需要的参数很多, 如4个以上的必填参数</p>
<p>分析: 额外损耗调用者的心智, 也是函数违背了单一职责的征兆.</p>
<h4 id="基本类型偏执（Primitive-Obsession）"><a href="#基本类型偏执（Primitive-Obsession）" class="headerlink" title="基本类型偏执（Primitive Obsession）"></a>基本类型偏执（Primitive Obsession）</h4><p>症状: 用基本类型来代表业务对象, 如钱、坐标、范围、人名等;</p>
<p>分析: 缺少必要的抽象, 容易诱发数据泥团, 也容易产生重复代码.</p>
<h4 id="过长的消息链（Message-Chains）"><a href="#过长的消息链（Message-Chains）" class="headerlink" title="过长的消息链（Message Chains）"></a>过长的消息链（Message Chains）</h4><p>症状: 超长的链式调用, 或者一长串取值函数, 或一长串临时变量.</p>
<p>分析: 系统难理解和维护, 客户端代码将与查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化，客户端就不得不做出相应修改。</p>
<h3 id="不当封装"><a href="#不当封装" class="headerlink" title="不当封装"></a>不当封装</h3><h4 id="发散式变化（Divergent-Change）"><a href="#发散式变化（Divergent-Change）" class="headerlink" title="发散式变化（Divergent Change）"></a>发散式变化（Divergent Change）</h4><p>症状: 一处代码包含了多个上下文, 也就是承担了多个职责.</p>
<p>分析: 难以测试; 增大修改的难度, 因为要关注多个上下文.</p>
<h4 id="霰弹式修改（Shotgun-Surgery）"><a href="#霰弹式修改（Shotgun-Surgery）" class="headerlink" title="霰弹式修改（Shotgun Surgery）"></a>霰弹式修改（Shotgun Surgery）</h4><p>症状: 一个上下文, 被分散在多处, 每遇到某种变化，你都必须在许多不同的类\函数\模块内做出许多小修改.</p>
<p>分析: 与发散式变化类似, 难以测试, 难以修改.</p>
<h4 id="依恋情结（Feature-Envy）"><a href="#依恋情结（Feature-Envy）" class="headerlink" title="依恋情结（Feature Envy）"></a>依恋情结（Feature Envy）</h4><p>症状: 一个函数跟另一个模块中的函数或者数据交流格外频繁，远胜于在自己所处模块内部的交流.</p>
<p>分析: 错误的抽象, 导致不同上下文的交汇, 难以测试.</p>
<h4 id="内幕交易（Insider-Trading）"><a href="#内幕交易（Insider-Trading）" class="headerlink" title="内幕交易（Insider Trading）"></a>内幕交易（Insider Trading）</h4><p>症状: 不同模块之间互相调用过多</p>
<p>分析: 说明这两个模块的封装不恰当, 模块的函数或者数据放错地方了.</p>
<h3 id="缺少抽象"><a href="#缺少抽象" class="headerlink" title="缺少抽象"></a>缺少抽象</h3><h4 id="重复代码（Duplicated-Code）"><a href="#重复代码（Duplicated-Code）" class="headerlink" title="重复代码（Duplicated Code）"></a>重复代码（Duplicated Code）</h4><p>症状: 在一个以上的地点看到相同的代码结构</p>
<p>分析: 影响可读性, 阅读时需要花额外的精力，比较多个地方是否存在差异。</p>
<h4 id="过长函数（Long-Function）"><a href="#过长函数（Long-Function）" class="headerlink" title="过长函数（Long Function）"></a>过长函数（Long Function）</h4><p>症状: 函数很长(超过100行), 大量的参数和临时变量, 复杂的循环和分支</p>
<p>分析: 影响可读性, 逻辑复杂难以测试</p>
<h4 id="数据泥团（Data-Clumps）"><a href="#数据泥团（Data-Clumps）" class="headerlink" title="数据泥团（Data Clumps）"></a>数据泥团（Data Clumps）</h4><p>症状: 扎堆出现的多个数据项, 删掉众多数据中的一项, 其他数据就失去了意义; 例如: 多个类的相似属性, 多个函数的多个相同参数.</p>
<p>分析: 缺少必要的抽象, 开发者关注了过多细节; 难以修改</p>
<h4 id="重复的switch-（Repeated-Switches）"><a href="#重复的switch-（Repeated-Switches）" class="headerlink" title="重复的switch （Repeated Switches）"></a>重复的switch （Repeated Switches）</h4><p>症状: 出现在多个地方的相同switch语句, 或者连续的if\else.</p>
<p>分析: 是重复代码的一种, 也会引发霰弹式修改; 容易引发bug</p>
<h4 id="循环语句（Loops）"><a href="#循环语句（Loops）" class="headerlink" title="循环语句（Loops）"></a>循环语句（Loops）</h4><p>症状: 过多的循环, 嵌套的循环</p>
<p>分析: 难以一眼看出循环代码的意图, 常演变成在一个循环里做多件事情.</p>
<h4 id="过大的类（Large-Class"><a href="#过大的类（Large-Class" class="headerlink" title="过大的类（Large Class"></a>过大的类（Large Class</h4><p>症状: 单个类做太多事情, 有些字段或者函数关联性不高</p>
<p>分析: 与过长函数类似, 违背了单一职责原则.</p>
<h3 id="过度抽象"><a href="#过度抽象" class="headerlink" title="过度抽象"></a>过度抽象</h3><h4 id="冗赘的元素（Lazy-Element）"><a href="#冗赘的元素（Lazy-Element）" class="headerlink" title="冗赘的元素（Lazy Element）"></a>冗赘的元素（Lazy Element）</h4><p>症状: 可有可无的代码元素; 如: 只有一个方法的类, 只有一个子类的父类.</p>
<p>分析: 过度设计的典型, 难以应对变化. 如无必要, 勿增实体.</p>
<h4 id="夸夸其谈通用性（Speculative-Generality）"><a href="#夸夸其谈通用性（Speculative-Generality）" class="headerlink" title="夸夸其谈通用性（Speculative Generality）"></a>夸夸其谈通用性（Speculative Generality）</h4><p>症状: 为了通用性而预先设计的代码结构, 实际上去很少用到; 如: 预留许多钩子处理各种非必要的事情, 无实际意义的多层继承体系.</p>
<p>分析: 过度设计, 系统更难理解和维护。</p>
<h4 id="临时字段（Temporary-Field）"><a href="#临时字段（Temporary-Field）" class="headerlink" title="临时字段（Temporary Field）"></a>临时字段（Temporary Field）</h4><p>症状: 预留的字段或者变量, 仅为某种特定情况而设.</p>
<p>分析: 过度设计, 代码让人不易理解，因为你通常认为对象在所有时候都需要它的所有字段。</p>
<h4 id="中间人（Middle-Man）"><a href="#中间人（Middle-Man）" class="headerlink" title="中间人（Middle Man）"></a>中间人（Middle Man）</h4><p>症状: 没有必要的中间人对象, 过度使用委托. 如: 某个类的接口有一半的函数都委托给其他类.</p>
<p>分析: 过度设计</p>
<h3 id="不当抽象"><a href="#不当抽象" class="headerlink" title="不当抽象"></a>不当抽象</h3><h4 id="异曲同工的类（Alternative-Classes-with-Different-Interfaces）"><a href="#异曲同工的类（Alternative-Classes-with-Different-Interfaces）" class="headerlink" title="异曲同工的类（Alternative Classes with Different Interfaces）"></a>异曲同工的类（Alternative Classes with Different Interfaces）</h4><p>症状: 在不同继承体系的类, 做的事情却大同小异</p>
<p>分析: 重复代码的一种类型</p>
<h4 id="纯数据类（Data-Class）"><a href="#纯数据类（Data-Class）" class="headerlink" title="纯数据类（Data Class）"></a>纯数据类（Data Class）</h4><p>症状: 只有数据字段和读写函数的类</p>
<p>分析: 纯数据类常常意味着行为被放在了错误的地方, 使得难以追踪数据的修改.</p>
<h4 id="被拒绝的遗赠（Refused-Bequest）"><a href="#被拒绝的遗赠（Refused-Bequest）" class="headerlink" title="被拒绝的遗赠（Refused Bequest）"></a>被拒绝的遗赠（Refused Bequest）</h4><p>症状: 子类和父类的相似度很低; 如: 子类只需要父类的一个方法, 却继承了整个类.</p>
<p>分析: 意味着继承体系设计错误</p>
<h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><h4 id="神秘命名（Mysterious-Name）"><a href="#神秘命名（Mysterious-Name）" class="headerlink" title="神秘命名（Mysterious Name）"></a>神秘命名（Mysterious Name）</h4><p>症状: 不能清晰地表明自己的功能和用法, 或者随意地取无意义的名称.</p>
<p>分析: 对可读性有很大影响, 如果你想不出一个好名字，说明背后很可能潜藏着更深的设计问题。</p>
<h4 id="注释（Comments）"><a href="#注释（Comments）" class="headerlink" title="注释（Comments）"></a>注释（Comments）</h4><p>症状: 超长的的注释</p>
<p>分析: 意味着相关代码实现难以理解</p>
<h2 id="思维导图"><a href="#思维导图" class="headerlink" title="思维导图"></a>思维导图</h2><p><img data-src="https://image.ponder.work/mweb/2020-03-21-%E4%BB%A3%E7%A0%81%E7%9A%84%E5%9D%8F%E5%91%B3%E9%81%93.png" alt="代码的坏味道"></p>
<h2 id="坏味道与重构手法速查表"><a href="#坏味道与重构手法速查表" class="headerlink" title="坏味道与重构手法速查表"></a>坏味道与重构手法速查表</h2><table>
<thead>
<tr>
<th>坏味道（英文）</th>
<th>坏味道（中文）</th>
<th>原书页码</th>
<th>常用重构</th>
</tr>
</thead>
<tbody><tr>
<td>Alternative Classes with Different Interfaces</td>
<td>异曲同工的类</td>
<td>83</td>
<td>改变函数声明（124），搬移函数（198），提炼超类（375）</td>
</tr>
<tr>
<td>Comments</td>
<td>注释</td>
<td>84</td>
<td>提炼函数（106），改变函数声明（124），引入断言（302）</td>
</tr>
<tr>
<td>Data Class</td>
<td>纯数据类</td>
<td>83</td>
<td>封装记录（162），移除设值函数（331），搬移函数（198），提炼函数（106），拆分阶段（154）</td>
</tr>
<tr>
<td>Data Clumps</td>
<td>数据泥团</td>
<td>78</td>
<td>提炼类（182），引入参数对象（140），保持对象完整（319）</td>
</tr>
<tr>
<td>Divergent Change</td>
<td>发散式变化</td>
<td>76</td>
<td>拆分阶段（154），搬移函数（198），提炼函数（106），提炼类（182）</td>
</tr>
<tr>
<td>Duplicated Code</td>
<td>重复代码</td>
<td>72</td>
<td>提炼函数（106），移动语句（223），函数上移（350）</td>
</tr>
<tr>
<td>Feature Envy</td>
<td>依恋情结</td>
<td>77</td>
<td>搬移函数（198），提炼函数（106）</td>
</tr>
<tr>
<td>Global Data</td>
<td>全局数据</td>
<td>74</td>
<td>封装变量（132）</td>
</tr>
<tr>
<td>Insider Trading</td>
<td>内幕交易</td>
<td>82</td>
<td>搬移函数（198），搬移字段（207），隐藏委托关系（189），以委托取代子类（381），以委托取代超类（399）</td>
</tr>
<tr>
<td>Large Class</td>
<td>过大的类</td>
<td>82</td>
<td>提炼类（182），提炼超类（375），以子类取代类型码（362）</td>
</tr>
<tr>
<td>Lazy Element</td>
<td>冗赘的元素</td>
<td>80</td>
<td>内联函数（115），内联类（186），折叠继承体系（380）</td>
</tr>
<tr>
<td>Long Function</td>
<td>过长函数</td>
<td>73</td>
<td>提炼函数（106），以查询取代临时变量（178），引入参数对象（140），保持对象完整（319），以命令取代函数（337），分解条件表达式（260），以多态取代条件表达式（272），拆分循环（227）</td>
</tr>
<tr>
<td>Long Parameter List</td>
<td>过长参数列</td>
<td>74</td>
<td>以查询取代参数（324），保持对象完整（319），引入参数对象（140），移除标记参数（314），函数组合成类（144）</td>
</tr>
<tr>
<td>Loops</td>
<td>循环语句</td>
<td>79</td>
<td>以管道取代循环（231）</td>
</tr>
<tr>
<td>Message Chains</td>
<td>过长的消息链</td>
<td>81</td>
<td>隐藏委托关系（189），提炼函数（106），搬移函数（198）</td>
</tr>
<tr>
<td>Middle Man</td>
<td>中间人</td>
<td>81</td>
<td>移除中间人（192），内联函数（115），以委托取代超类（399），以委托取代子类（381）</td>
</tr>
<tr>
<td>Mutable Data</td>
<td>可变数据</td>
<td>75</td>
<td>封装变量（132），拆分变量（240），移动语句（223），提炼函数（106），将查询函数和修改函数分离（306），移除设值函数（331），以查询取代派生变量（248），函数组合成类（144），函数组合成变换（149），将引用对象改为值对象（252）</td>
</tr>
<tr>
<td>Mysterious Name</td>
<td>神秘命名</td>
<td>72</td>
<td>改变函数声明（124），变量改名（137），字段改名（244）</td>
</tr>
<tr>
<td>Primitive Obsession</td>
<td>基本类型偏执</td>
<td>78</td>
<td>以对象取代基本类型（174），以子类取代类型码（362），以多态取代条件表达式（272），提炼类（182），引入参数对象（140）</td>
</tr>
<tr>
<td>Refused Bequest</td>
<td>被拒绝的遗赠</td>
<td>83</td>
<td>函数下移（359），字段下移（361），以委托取代子类（381），以委托取代超类（399）</td>
</tr>
<tr>
<td>Repeated Switches</td>
<td>重复的switch</td>
<td>79</td>
<td>以多态取代条件表达式（272）</td>
</tr>
<tr>
<td>Shotgun Surgery</td>
<td>霰弹式修改</td>
<td>76</td>
<td>搬移函数（198），搬移字段（207），函数组合成类（144），函数组合成变换（149），拆分阶段（154），内联函数（115），内联类（186）</td>
</tr>
<tr>
<td>Speculative Generality</td>
<td>夸夸其谈通用性</td>
<td>80</td>
<td>折叠继承体系（380），内联函数（115），内联类（186），改变函数声明（124），移除死代码（237）</td>
</tr>
<tr>
<td>Temporary Field</td>
<td>临时字段</td>
<td>80</td>
<td>提炼类（182），搬移函数（198），引入特例（289）</td>
</tr>
</tbody></table>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>重构</tag>
      </tags>
  </entry>
  <entry>
    <title>除法在 Python2 和 Python3 中的区别</title>
    <url>/2016/01/10/%E9%99%A4%E6%B3%95%E5%9C%A8python2%E5%92%8Cpython3%E4%B8%AD%E7%9A%84%E5%8C%BA%E5%88%AB/</url>
    <content><![CDATA[<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol>
<li>Python2 中使用<code>from __future__ import division</code>就可以使用python3的除法。</li>
<li>Python2 中<code>/</code>与操作数有关，<code>x / y</code>中x、y都为整型的话，为<code>floor除法</code>，否则为<code>true除法</code>也是日常的除法。</li>
<li>Python3 中<code>/</code>为<code>true除法</code>， 与操作数无关。</li>
<li><code>//</code>在 Python2 与 Python3 中并无差别， 都代表<code>floor除法</code></li>
</ol>
<h2 id="Python3"><a href="#Python3" class="headerlink" title="Python3"></a>Python3</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>-<span class="number">5</span>/<span class="number">3</span></span><br><span class="line">-<span class="number">1.6666666666666667</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>-<span class="number">5</span>//<span class="number">3</span></span><br><span class="line">-<span class="number">2</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>-<span class="number">5.0</span>/<span class="number">3</span></span><br><span class="line">-<span class="number">1.6666666666666667</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>-<span class="number">5.0</span>//<span class="number">3</span></span><br><span class="line">-<span class="number">2.0</span></span><br></pre></td></tr></table></figure>

<h2 id="Python2"><a href="#Python2" class="headerlink" title="Python2"></a>Python2</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>-<span class="number">5</span>/<span class="number">3</span></span><br><span class="line">-<span class="number">2</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>-<span class="number">5</span>//<span class="number">3</span></span><br><span class="line">-<span class="number">2</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>-<span class="number">5.0</span>/<span class="number">3</span></span><br><span class="line">-<span class="number">1.6666666666666667</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>-<span class="number">5.0</span>//<span class="number">3</span></span><br><span class="line">-<span class="number">2.0</span></span><br></pre></td></tr></table></figure>
]]></content>
      <categories>
        <category>编程</category>
      </categories>
  </entry>
  <entry>
    <title>《改善python的91建议》笔记</title>
    <url>/2016/06/05/%E6%94%B9%E5%96%84python%E7%9A%8491%E5%BB%BA%E8%AE%AE%20%E7%AC%94%E8%AE%B0/</url>
    <content><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>文如其名，是由91篇关系不是很紧密的python文章集合而成<br>这本书和国内很多技术类书籍一样，排版的代码缩进很有问题，还有文章有些观点并不正确。</p>
<p>除此之外，还是有很多有用的知识点，需要读者自己辨别。</p>
<h2 id="笔记"><a href="#笔记" class="headerlink" title="笔记"></a>笔记</h2><h3 id="Python的包和模块规范"><a href="#Python的包和模块规范" class="headerlink" title="Python的包和模块规范"></a>Python的包和模块规范</h3><p>结构日益规范化。现在的库或框架跟随了以下潮流：</p>
<ul>
<li>包和模块的命名采用小写、单数形式，而且短小。</li>
<li>包通常仅作为命名空间，如只包含空的<code>__init__</code>.py文件。</li>
</ul>
<h3 id="x-y-y-x赋值"><a href="#x-y-y-x赋值" class="headerlink" title="x, y&#x3D;y, x赋值"></a>x, y&#x3D;y, x赋值</h3><p>Python表达式计算的顺序说起。</p>
<p>一般情况下Python表达式的计算顺序是从左到右，但遇到表达式赋值的时候表达式右边的操作数先于左边的操作数计算，因此表达式<code>expr3, expr4=exprl, expr2</code>的计算顺序是<code>exprl, expr2 —&gt; expr3, expr4</code>。</p>
<p>因此对于表达式<code>x, y=y, x</code>,其在内存中执行的顺序如下：</p>
<ol>
<li>先计算右边的表达式<code>y,x</code>,因此先在内存中创建元组<code>(y,x)</code>，其标示符和值分别为y、x及其对应的值，其中y和x是在初始化时已经存在于内存中的对象。</li>
<li>计算表达式左边的值并进行赋值，元组被依次分配给左边的标示符，通过解压缩(unpacking),元组第一标识符（为y)分配给左边第一个元素（此时为X)，元组第二个标识符(为x)分配给第二个元素（此时为y),从而达到x、y值交换的目的。</li>
</ol>
<h3 id="提高and、or表达式的效率"><a href="#提高and、or表达式的效率" class="headerlink" title="提高and、or表达式的效率"></a>提高and、or表达式的效率</h3><p>python的and、or表达式并不会将每个值都算出，一旦整个表达式的值已知，其他部分就不会被计算，并且返回最后计算的那个值。<br>因此，在编程过程中，如果对于or条件表达式应该将<code>值为真可能性较高的变量</code>写在or的前面；<br>而对于and则相反，应该推后。</p>
<span id="more"></span>

<h3 id="防止注入攻击"><a href="#防止注入攻击" class="headerlink" title="防止注入攻击"></a>防止注入攻击</h3><ul>
<li><code>().__class__.__bases__[0].__subclasses__</code>可返回当前解释器进程中的所有类对象。</li>
<li><code>__import__(&quot;os&quot;).system(&quot;dir&quot;)</code>使用<code>__import__</code>可以导入模块，并且返回这个模块</li>
</ul>
<h3 id="不要在Python中使用-i"><a href="#不要在Python中使用-i" class="headerlink" title="不要在Python中使用++i"></a>不要在Python中使用++i</h3><p>因为Python解释器会将<code>++i</code>操作解释为<code>+(+i)</code>,其中<code>+</code>表示正数符号。对于<code>--i</code>操作也是类似。</p>
<h3 id="for与while中的else子句"><a href="#for与while中的else子句" class="headerlink" title="for与while中的else子句"></a>for与while中的else子句</h3><p>循环如果<strong>正常结束</strong>（没有break），则执行else子句。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">10</span>):</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">if</span> i &lt; <span class="number">4</span>:</span><br><span class="line"><span class="meta">... </span>        <span class="built_in">print</span> i</span><br><span class="line"><span class="meta">... </span><span class="keyword">else</span>:</span><br><span class="line"><span class="meta">... </span>    <span class="built_in">print</span> <span class="string">&#x27;for loop completed&#x27;</span></span><br><span class="line"><span class="number">0</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="number">2</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="keyword">for</span> loop completed</span><br></pre></td></tr></table></figure>

<h3 id="不要在try…except的finally子句中return或break"><a href="#不要在try…except的finally子句中return或break" class="headerlink" title="不要在try…except的finally子句中return或break"></a>不要在try…except的finally子句中return或break</h3><ol>
<li>当try块中发生异常的时候，如果在except语句中找不到对应的异常处理，异常将会被临时保存起来。</li>
<li>当finally执行完毕的时候，临时保存的异常将会再次被抛出，但如果finally语句中产生了新的异常或者执行了return或者break语句，那么临时保存的异常将会被丢失，从而导致异常屏蔽。</li>
<li>在实际应用程序开发过程中，并不推荐在finally中使用return语句进行返回，这种处理方式不仅会带来误解而且可能会引起非常严重的错误。</li>
</ol>
<h3 id="nonzero-方法约定了如何判断对象的真假"><a href="#nonzero-方法约定了如何判断对象的真假" class="headerlink" title="__nonzero__()方法约定了如何判断对象的真假"></a><code>__nonzero__()</code>方法约定了如何判断对象的真假</h3><p>该内部方法用于对自身对象迸行空值测试，返回<code>0/1</code>或<code>True/False</code>。</p>
<p>如果一个对象没有定义该方法，Python将获取<code>__len__()</code>方法调用的结果来进行判断。<code>__len__()</code>返回值为0则表示为空。</p>
<p>如果一个类中既没有定义<code>__len__()</code>方法也没有定义<code>__nonzero__()</code>方法，该类的实例用if判断的结果都为True。</p>
<h3 id="字符串是不可变对象"><a href="#字符串是不可变对象" class="headerlink" title="字符串是不可变对象"></a>字符串是不可变对象</h3><p>对于不可变对象来说，当我们对其进行相关操作的时候，Python实际上仍然保持原来的值而是重新创建一个新的对象，所以字符串对象不允许以索引的方式进行賦值.</p>
<p>当有两个对象同时指向一个字符串对象的时候，对其中一个对象的操作并不会影响另一个对象。</p>
<h3 id="注意-args与-kwargs与默认参数"><a href="#注意-args与-kwargs与默认参数" class="headerlink" title="注意*args与**kwargs与默认参数"></a>注意<code>*args</code>与<code>**kwargs</code>与默认参数</h3><p>首先满足普通参数，然后是默认参数;<br>如果剩余的参数个数能够覆盖所有的默认参数，则默认参数会使用传递时候的值；<br>如果剩余参数个数不够，则尽最大可能满足默认参数的值（从前往后）。<br>除此之外其余的参数除了键值对以外所有的参数都将作为args的可变参数，kwargs则与键值对对应。<br>若kwargs中有键与默认参数重复，会抛出<code>TypeError</code>。</p>
<h3 id="关于排序"><a href="#关于排序" class="headerlink" title="关于排序"></a>关于排序</h3><ol>
<li><p><code>sorted()</code>与<code>sort()</code><br>从函数的定义形式可以看出，sorted()接收任意可迭代的对象作为参数，返回排序后对象，而sort()—般作用于列表（是bound method），原位操作。针对元组使用sort()方法会抛出<code>AttributeError</code>，而使用sorted()函数则没有这个问题。</p>
</li>
<li><p>dict或者嵌套list排序，注意<code>itemgetter</code>用法</p>
</li>
</ol>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> operator <span class="keyword">import</span> itemgetter</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>itemgetter(<span class="number">1</span>)(<span class="string">&#x27;ABCDEFG&#x27;</span>)</span><br><span class="line"><span class="string">&#x27;B&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>itemgetter(<span class="number">1</span>,<span class="number">3</span>,<span class="number">5</span>)(<span class="string">&#x27;ABCDEFG&#x27;</span>)</span><br><span class="line">(<span class="string">&#x27;B&#x27;</span>, <span class="string">&#x27;D&#x27;</span>, <span class="string">&#x27;F&#x27;</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>itemgetter(<span class="built_in">slice</span>(<span class="number">2</span>,<span class="literal">None</span>))(<span class="string">&#x27;ABCDEFG&#x27;</span>)</span><br><span class="line"><span class="string">&#x27;CDEFG&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>phonebook = &#123;<span class="string">&#x27;Linda&#x27;</span>: <span class="string">&#x27;7750&#x27;</span>, <span class="string">&#x27;Bob&#x27;</span>: <span class="string">&#x27;9345&#x27;</span>, <span class="string">&#x27;Carol&#x27;</span>: <span class="string">&#x27;5834&#x27;</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">sorted</span>(phonebook.iteritems(),key=itemgetter(<span class="number">1</span>))</span><br><span class="line">[(<span class="string">&#x27;Carol&#x27;</span>, <span class="string">&#x27;5834&#x27;</span>), (<span class="string">&#x27;Linda&#x27;</span>, <span class="string">&#x27;7750&#x27;</span>), (<span class="string">&#x27;Bob&#x27;</span>, <span class="string">&#x27;9345&#x27;</span>)]</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>gameresult = [[<span class="string">&#x27;Bob&#x27;</span>,<span class="number">95.00</span>,<span class="string">&#x27;A&#x27;</span>], [<span class="string">&#x27;Alan&#x27;</span>,<span class="number">86.0</span>,<span class="string">&#x27;C&#x27;</span>], [<span class="string">&#x27;Mandy&#x27;</span>,<span class="number">82.5</span>,<span class="string">&#x27;A&#x27;</span>],[<span class="string">&#x27;Rob&#x27;</span>,<span class="number">86</span>,<span class="string">&#x27;E&#x27;</span>]]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">sorted</span>(gameresult, key=itemgetter(<span class="number">2</span>,<span class="number">1</span>))</span><br><span class="line">[[<span class="string">&#x27;Mandy&#x27;</span>, <span class="number">82.5</span>, <span class="string">&#x27;A&#x27;</span>], [<span class="string">&#x27;Bob&#x27;</span>, <span class="number">95.0</span>, <span class="string">&#x27;A&#x27;</span>], [<span class="string">&#x27;Alan&#x27;</span>, <span class="number">86.0</span>, <span class="string">&#x27;C&#x27;</span>], [<span class="string">&#x27;Rob&#x27;</span>, <span class="number">86</span>, <span class="string">&#x27;E&#x27;</span>]]</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>mydict=&#123;<span class="string">&#x27;Li&#x27;</span>:[<span class="string">&#x27;M&#x27;</span>, <span class="number">7</span>], <span class="string">&#x27;Zhang&#x27;</span>:[<span class="string">&#x27;E&#x27;</span>,<span class="number">2</span>], <span class="string">&#x27;Wang&#x27;</span>: [<span class="string">&#x27;P&#x27;</span>, <span class="number">3</span>], <span class="string">&#x27;Du&#x27;</span>:[<span class="string">&#x27;C&#x27;</span>,<span class="number">2</span>], <span class="string">&#x27;Ma&#x27;</span>: [<span class="string">&#x27;C&#x27;</span>, <span class="number">9</span>], <span class="string">&#x27;Zhe&#x27;</span>:[<span class="string">&#x27;H&#x27;</span>, <span class="number">7</span>]&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">sorted</span>(mydict.iteritems(), key=<span class="keyword">lambda</span> (k,v): itemgetter(<span class="number">1</span>)(v))</span><br><span class="line">[(<span class="string">&#x27;Zhang&#x27;</span>, [<span class="string">&#x27;E&#x27;</span>, <span class="number">2</span>]), (<span class="string">&#x27;Du&#x27;</span>, [<span class="string">&#x27;C&#x27;</span>, <span class="number">2</span>]), (<span class="string">&#x27;Wang&#x27;</span>, [<span class="string">&#x27;P&#x27;</span>, <span class="number">3</span>]), (<span class="string">&#x27;Li&#x27;</span>, [<span class="string">&#x27;M&#x27;</span>, <span class="number">7</span>]), (<span class="string">&#x27;Zhe&#x27;</span>, [<span class="string">&#x27;H&#x27;</span>, <span class="number">7</span>]), (<span class="string">&#x27;Ma&#x27;</span>, [<span class="string">&#x27;C&#x27;</span>, <span class="number">9</span>])]</span><br></pre></td></tr></table></figure>

<h3 id="元素频次统计"><a href="#元素频次统计" class="headerlink" title="元素频次统计"></a>元素频次统计</h3><p>使用collections.Counter来对可迭代对象统计各元素出现次数，Counter类是dict的子类。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> collections <span class="keyword">import</span> Counter</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Counter(<span class="string">&#x27;qqqaaaasdfsdf&#x27;</span>)</span><br><span class="line">Counter(&#123;<span class="string">&#x27;a&#x27;</span>: <span class="number">4</span>, <span class="string">&#x27;q&#x27;</span>: <span class="number">3</span>, <span class="string">&#x27;s&#x27;</span>: <span class="number">2</span>, <span class="string">&#x27;d&#x27;</span>: <span class="number">2</span>, <span class="string">&#x27;f&#x27;</span>: <span class="number">2</span>&#125;)</span><br></pre></td></tr></table></figure>

<h3 id="处理csv用pandas模块"><a href="#处理csv用pandas模块" class="headerlink" title="处理csv用pandas模块"></a>处理csv用pandas模块</h3><p>使用pandas处理大型CSV文件。</p>
<p>Pandas即PythonDataAnalysisLibrary,是为了解决数据分析而创建的第三方工具，它不仅提供了丰富的数据模型，而且支持多种文件格式处理，包括CSV、HDF5、HTML等，能够提供高效的大型数据处理。</p>
<h3 id="解析xml使用cElementTree或lxml"><a href="#解析xml使用cElementTree或lxml" class="headerlink" title="解析xml使用cElementTree或lxml"></a>解析xml使用cElementTree或lxml</h3><p>一般情况使用ElementTree解析XML。</p>
<p>cElementTree是ElementTree的Cython实现，速度更快，消耗内存更少，性能上更占优势，在实际使用过程中应该尽量优先使用cElementTree。一般情况指的是XML文件大小适中，对性能要求并非非常严格。<br>如果在实际过程中需要处理的XML文件大小在GB或近似GB级别，第三方模块lxml会获得较优的处理结果。</p>
<h3 id="pickle与对象序列化"><a href="#pickle与对象序列化" class="headerlink" title="pickle与对象序列化"></a>pickle与对象序列化</h3><p>pickle估计是最通用的序列化模块了。<br>它还有个C语言的实现cPickle，相比pickle来说具有较好的性能，其速度大概是pickle的1000倍，因此在大多数应用程序中应该优先使用cPickle(注：cPickle除了不能被继承之外，它们两者的使用基本上区别不大)。<br>pickle中最主要的两个函数对为dump()和load(),分别用来进行对象的序列化和反序列化。</p>
<h3 id="logging非进程安全"><a href="#logging非进程安全" class="headerlink" title="logging非进程安全"></a>logging非进程安全</h3><p>Logging只是线程安全的，不支持多进程写人同一个日子文件.<br>因此对于多个进程，需要配置不同的日志文件，否则会出现log覆盖。</p>
<h3 id="mixin运行时动态改变基类"><a href="#mixin运行时动态改变基类" class="headerlink" title="mixin运行时动态改变基类"></a>mixin运行时动态改变基类</h3><p>用mixin模式让程序更加灵活.，每个类都有<code>__bases__</code>属性，它是一个元组，用来存放所有的基类。<br>与其他静态语言不同，Python语言中的基类在运行中可以动态改变。所以当我们向其中增加新的基类时，这个类就拥有了新的方法，也就是所谓的混人（mixin）。这种动态性的好处在于代码获得了更丰富的扩展功能。</p>
<h3 id="init-与-new-哪个才是构造方法"><a href="#init-与-new-哪个才是构造方法" class="headerlink" title="__init__()与__new__()哪个才是构造方法"></a><code>__init__()</code>与<code>__new__()</code>哪个才是构造方法</h3><p><code>__init__()</code>并不是真正意义上的构造方法，<code>__init__()</code>方法所做的工作是在类的对象创建好之后进行变量的初始化。</p>
<p><code>__new__()</code>方法才会真正创建实例，是类的构造方法。<code>__new__()</code>方法是静态方法，而<code>__init__()</code>为实例方法。<br><code>__new__()</code>方法一般需要返回类的对象，当返回类的对象时将会自动调用<code>__init__()</code>方法进行初始化，如果没有对象返回，则<code>__init__()</code>方法不会被调用。<br><code>__init__()</code>方法不需要显式返回，默认为<code>None</code>,否则会在运行时抛出<code>TypeError</code>。<strong>（新式类）</strong></p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">MyC</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta">... </span>    @<span class="built_in">staticmethod</span></span><br><span class="line"><span class="meta">... </span>    <span class="keyword">def</span> <span class="title function_">__new__</span>(<span class="params">cls,*args</span>):</span><br><span class="line"><span class="meta">... </span>        <span class="keyword">pass</span></span><br><span class="line"><span class="meta">... </span>    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line"><span class="meta">... </span>        <span class="built_in">print</span> <span class="string">&#x27;aa&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = MyC() </span><br><span class="line"><span class="comment"># 此处的__init__()方法没被调用</span></span><br><span class="line"><span class="comment">###################################</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">MyC</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta">... </span>    @<span class="built_in">staticmethod</span></span><br><span class="line"><span class="meta">... </span>    <span class="keyword">def</span> <span class="title function_">__new__</span>(<span class="params">cls,*args</span>):</span><br><span class="line"><span class="meta">... </span>        <span class="keyword">return</span> <span class="built_in">super</span>(MyC, cls).__new__(cls, *args)</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line"><span class="meta">... </span>        <span class="built_in">print</span> <span class="string">&#x27;aa&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = MyC()</span><br><span class="line">aa</span><br></pre></td></tr></table></figure>

<h3 id="重写-getattr-和-getattribute-的注意事项"><a href="#重写-getattr-和-getattribute-的注意事项" class="headerlink" title="重写__getattr__()和__getattribute__()的注意事项"></a>重写<code>__getattr__()</code>和<code>__getattribute__()</code>的注意事项</h3><ol>
<li>在覆盖类的<code>__getattr__()</code>和<code>__getattribute__()</code>方法的时候需要特别小心，否则可能出现无穷递归。</li>
<li>覆盖<code>__getattribute__()</code>方法之后，任何属性的访问都会调用用户定义的<code>__getattribute__()</code>方法，性能上会有所损耗，比使用默认的方法要慢。</li>
<li>覆盖的<code>__getattr__()</code>方法如果能够动态处理事先未定义的属性，可以更好地实现数据隐藏。因为dir()通常只显示正常的属性和方法，因此不会将该属性列为可用属性</li>
</ol>
<h3 id="metaclass-与元编程"><a href="#metaclass-与元编程" class="headerlink" title="__metaclass__与元编程"></a><code>__metaclass__</code>与元编程</h3><p>在新式类中当一个类未设置<code>__metaclass__</code>属性的时候，它将使用默认的type元类来生成类。而当该属性被设置时查找规则如下：</p>
<ol>
<li>如果存在<code>__dict__[&#39;__metadass__&#39;]</code>，则使用对应的值来构建类；否则使用其父类<code>__dict__[&#39;__metaclass__&#39;]</code>中所指定的元类来构建类，当父类中也不存在指定的metadass的情形下使用默认元类type。</li>
<li>对于古典类，条件1不满足的情况下，如果存在全局变量<code>__metaclass__</code>，则使用该变量所对应的元类来构建类；否则使用<code>types.ClassType</code>。元方法可以从元类或者类中调用，而不能从类的实例中调用；但类方法可以从类中调用，也可以从类的实例中调用。</li>
</ol>
<h3 id="hash-与可哈希"><a href="#hash-与可哈希" class="headerlink" title="__hash__()与可哈希"></a><code>__hash__()</code>与可哈希</h3><p>可哈希对象，它是通过<code>__hash__()</code>这个内置函数的，这在创建自己的类型时非常有用，因为只有支持可哈希协议的类型才能作为dict的键类型（不过只要继承自object的新式类默认就支持了）。</p>
<h3 id="生成器用作上下文管理器。"><a href="#生成器用作上下文管理器。" class="headerlink" title="生成器用作上下文管理器。"></a>生成器用作上下文管理器。</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">from</span> contextlib <span class="keyword">import</span> contextmanager</span><br><span class="line"><span class="meta">... </span>@contextmanager</span><br><span class="line"><span class="meta">... </span><span class="keyword">def</span> <span class="title function_">tag</span>(<span class="params">name</span>):</span><br><span class="line"><span class="meta">... </span>	<span class="built_in">print</span><span class="string">&quot;&lt;%s&gt;&quot;</span>%name</span><br><span class="line"><span class="meta">... </span>	<span class="keyword">yield</span></span><br><span class="line"><span class="meta">... </span>	<span class="built_in">print</span><span class="string">&quot;&lt;/%s&gt;&quot;</span>%name</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">with</span> tag(<span class="string">&quot;h1&quot;</span>):</span><br><span class="line"><span class="meta">... </span>	<span class="built_in">print</span><span class="string">&quot;foo&quot;</span></span><br><span class="line">&lt;h1&gt;</span><br><span class="line">foo</span><br><span class="line">&lt;/h1&gt;</span><br></pre></td></tr></table></figure>

]]></content>
      <categories>
        <category>编程</category>
      </categories>
      <tags>
        <tag>读书笔记</tag>
        <tag>Python</tag>
      </tags>
  </entry>
</search>
