<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Prodesire</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://prodesire.cn/"/>
  <updated>2020-07-24T15:00:04.360Z</updated>
  <id>http://prodesire.cn/</id>
  
  <author>
    <name>Prodesire</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Python 命令行大乱斗</title>
    <link href="http://prodesire.cn/2020/07/02/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%A4%A7%E4%B9%B1%E6%96%97/"/>
    <id>http://prodesire.cn/2020/07/02/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%A4%A7%E4%B9%B1%E6%96%97/</id>
    <published>2020-07-01T16:01:11.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<p>本文首发于 <a href="https://mp.weixin.qq.com/s/szn0qm8_u6GTbVoW9GBP3Q" target="_blank" rel="noopener">凌云时刻公众号</a>。</p><p>当你想实现一个命令行程序时，或许第一个想到的是用 Python 来实现。比如 CentOS 上大名鼎鼎的包管理工具 <code>yum</code> 就是基于 Python 实现的。</p><p>而 Python 的世界中有很多命令行库，每个库都各具特色。但我们往往不知道其背后的设计理念，也因此在选择时感到迷茫。这些库的作者为何在重复造轮子，他是从哪个角度来考虑，来让命令行库“演变”到一个新的更好用的形态。</p><p>为了能够更加直观地感受到命令行库的设计理念，在此之前，我们不妨设计一个名为 <code>calc</code> 的命令行程序，它能：</p><ul><li>支持 <code>echo</code> 子命令，对输入的字符串做处理来输出<ul><li>若不提供任何选项，则输出原始内容</li><li>若提供 <code>--lower</code> 选项，则输出小写字符串</li><li>若提供 <code>--upper</code> 选项，则输出大写字符串</li></ul></li><li>支持 <code>eval</code> 子命令，针对输入调用 Python 的 <code>eval</code> 函数，将结果输出（作为示例，我们不考虑安全性问题）</li></ul><a id="more"></a><h2 id="argparse"><a href="#argparse" class="headerlink" title="argparse"></a>argparse</h2><p><a href="https://docs.python.org/3/library/argparse.html" target="_blank" rel="noopener">argparse</a> 作为 Python 的标准库，可能会是你想到第一个命令行库。</p><p><code>argparse</code> 的设计理念就是提供给开发者最细粒度的控制。换句话说，你需要告诉它必不可少的细节，比如参数的类型是什么，处理参数的动作是怎样的。</p><p>在 <code>argparse</code> 的世界中，需要：</p><ul><li>设置解析器，作为后续定义参数和解析命令行的基础。如果要实现子命令，则还要设置子解析器。</li><li>定义参数，包括名称、类型、动作、帮助等。其中的动作是指对于此参数的初步处理，是直接存下来，还是作为布尔值，亦或是追加到列表中等等</li><li>解析参数</li><li>根据参数编写业务逻辑</li></ul><p>以下示例是基于 <code>argparse</code> 的 <code>calc</code> 程序：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> argparse</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">echo_text</span><span class="params">(args)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> args.lower:</span><br><span class="line">        print(args.text.lower())</span><br><span class="line">    <span class="keyword">elif</span> args.upper:</span><br><span class="line">        print(args.text.upper())</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        print(args.text)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">eval_expression</span><span class="params">(args)</span>:</span></span><br><span class="line">    print(eval(args.expression))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 设置解析器</span></span><br><span class="line">parser = argparse.ArgumentParser(description=<span class="string">'Calculator Program.'</span>)</span><br><span class="line">subparsers = parser.add_subparsers()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 定义参数</span></span><br><span class="line"><span class="comment"># 2.1 echo 子命令</span></span><br><span class="line"><span class="comment"># echo 子解析器</span></span><br><span class="line">echo_parser = subparsers.add_parser(</span><br><span class="line">    <span class="string">'echo'</span>, help=<span class="string">'Echo input text in multiple forms'</span>)</span><br><span class="line"><span class="comment"># 添加位置参数 text</span></span><br><span class="line">echo_parser.add_argument(<span class="string">'text'</span>, help=<span class="string">'Input text'</span>)</span><br><span class="line"><span class="comment"># --lower/--upper 互斥，需要设置互斥组</span></span><br><span class="line">echo_group = echo_parser.add_mutually_exclusive_group()</span><br><span class="line"><span class="comment"># 添加选项参数 --lower/--upper，这里action的作用就是将之变为布尔变量</span></span><br><span class="line">echo_parser.add_argument(<span class="string">'--lower'</span>, action=<span class="string">'store_true'</span>, help=<span class="string">'Lower input text'</span>)</span><br><span class="line">echo_parser.add_argument(<span class="string">'--upper'</span>, action=<span class="string">'store_true'</span>, help=<span class="string">'Upper input text'</span>)</span><br><span class="line"><span class="comment"># 设置此命令的处理函数</span></span><br><span class="line">echo_parser.set_defaults(handle=echo_text)</span><br><span class="line"></span><br><span class="line"><span class="comment"># eval 子解析器</span></span><br><span class="line">eval_parser = subparsers.add_parser(</span><br><span class="line">    <span class="string">'eval'</span>, help=<span class="string">'Eval input expression and return result'</span>)</span><br><span class="line"><span class="comment"># 添加位置参数 expression</span></span><br><span class="line">eval_parser.add_argument(<span class="string">'expression'</span>, help=<span class="string">'Expression to eval'</span>)</span><br><span class="line"><span class="comment"># 设置此命令的处理函数</span></span><br><span class="line">eval_parser.set_defaults(handle=eval_expression)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 解析参数</span></span><br><span class="line">args = parser.parse_args([<span class="string">'echo'</span>, <span class="string">'--upper'</span>, <span class="string">'Hello, World'</span>])</span><br><span class="line">print(args)  <span class="comment"># 结果：Namespace(lower=True, text='Hello, World', upper=False)</span></span><br><span class="line"><span class="comment"># args = parser.parse_args(['eval', '1+2*3'])</span></span><br><span class="line"><span class="comment"># print(args)  # 结果：Namespace(expression='1+2*3')</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 业务逻辑处理</span></span><br><span class="line">args.handle(args)</span><br></pre></td></tr></table></figure><p>从上述示例可以看到，要实现子命令，对应地需要添加子解析器。然后最为关键的就是要定义参数，需要通过 <code>add_argument</code> 很明确地告诉 <code>argparse</code> 参数长什么样，需要怎么处理：</p><ul><li>它是位置参数 <code>text</code>/<code>expression</code>，还是选项参数 <code>--lower</code>/<code>--upper</code></li><li>若是选项参数，是否互斥</li><li>参数的是存成什么形式，比如 <code>action=&#39;store_true&#39;</code> 表示存成布尔</li><li>子命令的响应函数</li></ul><p>通过 <code>argparse</code> 实现的整个过程是很计算机思维的，且比较冗长。其优点是灵活，所有的功能都涵盖到了；但缺点则是将定义和处理割裂，尤其在程序功能复杂时会愈加凌乱和不直观，难以理解和维护。</p><h2 id="docopt"><a href="#docopt" class="headerlink" title="docopt"></a>docopt</h2><p>有人喜欢 <code>argparse</code> 这样命令式的写法，就会有人喜欢声明式的写法。而 <a href="http://docopt.org/" target="_blank" rel="noopener">docopt</a> 恰巧这就是这样一个命令行库。设计它的初衷就是对于熟悉命令行程序帮助信息的开发者来说，直接通过编写帮助信息来描述整个命令行参数定义的元信息会是更加简单快捷的方式。这种声明式的语法描述某种程度上会比过程式地定义参数来的更加简单和直观。</p><p>在 <code>docopt</code> 的世界中，需要：</p><ul><li>定义接口描述/帮助信息，这一步是它的特色和重点</li><li>解析参数，获得一个字典</li><li>根据参数编写业务逻辑</li></ul><p>以下示例是基于 <code>docopt</code> 的 <code>calc</code> 程序：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 定义接口描述/帮助信息</span></span><br><span class="line"><span class="string">"""Calculator Program.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Usage:</span></span><br><span class="line"><span class="string">  calc echo [--lower | --upper] &lt;text&gt;</span></span><br><span class="line"><span class="string">  calc eval &lt;expression&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Commands:</span></span><br><span class="line"><span class="string">  echo          Echo input text in multiple forms</span></span><br><span class="line"><span class="string">  eval          Eval input expression and return result</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Options:</span></span><br><span class="line"><span class="string">  -h --help     Show help</span></span><br><span class="line"><span class="string">  --lower       Lower input text</span></span><br><span class="line"><span class="string">  --upper       Upper input text</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="keyword">from</span> docopt <span class="keyword">import</span> docopt</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">echo_text</span><span class="params">(args)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> args[<span class="string">'--lower'</span>]:</span><br><span class="line">        print(args[<span class="string">'&lt;text&gt;'</span>].lower())</span><br><span class="line">    <span class="keyword">elif</span> args[<span class="string">'--upper'</span>]:</span><br><span class="line">        print(args[<span class="string">'&lt;text&gt;'</span>].upper())</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        print(args[<span class="string">'&lt;text&gt;'</span>])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">eval_expression</span><span class="params">(args)</span>:</span></span><br><span class="line">    print(eval(args[<span class="string">'&lt;expression&gt;'</span>]))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 解析命令行</span></span><br><span class="line">args = docopt(__doc__, argv=[<span class="string">'echo'</span>, <span class="string">'--upper'</span>, <span class="string">'Hello, World'</span>])</span><br><span class="line"><span class="comment"># 结果：&#123;'--lower': False, '--upper': True, '&lt;expression&gt;': None, '&lt;text&gt;': 'Hello, World', 'echo': True, 'eval': False&#125;</span></span><br><span class="line">print(args)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 业务逻辑</span></span><br><span class="line"><span class="keyword">if</span> args[<span class="string">'echo'</span>]:</span><br><span class="line">    echo_text(args)</span><br><span class="line"><span class="keyword">elif</span> args[<span class="string">'eval'</span>]:</span><br><span class="line">    eval_expression(args)</span><br></pre></td></tr></table></figure><p>从上述示例可以看到，我们通过文档字符串 <code>__doc__</code> 定义了接口描述，这和 <code>argparse</code> 中 一系列参数定义的行为是等价的，然后 <code>docopt</code> 便会根据这个元信息把命令行参数转换为一个字典。业务逻辑中就需要对这个字典进行处理。</p><p>相比于 <code>argparse</code>：</p><ul><li>对于较为复杂的命令，命令和参数元信息的定义上 <code>docopt</code> 会更加简单</li><li>在业务逻辑的处理上，<code>argparse</code> 在一些简单参数的处理上会更加便捷，且命令和处理函数之间可以方便路由（比如示例中的情形）；相对来说 <code>docopt</code> 转换为字典后就把所有处理交给业务逻辑的方式会更加复杂</li></ul><h2 id="click"><a href="#click" class="headerlink" title="click"></a>click</h2><p>不论是 <code>argparse</code> 还是 <code>docopt</code>，元信息的定义和处理都是割裂开的。而命令行程序本质上是定义参数并对参数进行处理，而处理参数的逻辑一定是与所定义的参数有关联的。那可不可以用函数和装饰器来实现处理参数逻辑与定义参数的关联呢？<a href="https://click.palletsprojects.com/en/7.x/" target="_blank" rel="noopener">click</a> 正好就是以这种使用方式来设计的。</p><p>装饰器这样一个优雅的语法糖是元信息定义和处理逻辑之间的绝妙胶水，从而暗示了两者的路有关系。对比于前两个命令行库的路由实现着实优雅了不少。</p><p>在 <code>click</code> 的世界中：</p><ul><li>通过装饰器定义命令和参数的元信息</li><li>使用此装饰器装饰处理函数</li></ul><p>对，就是这么简单。</p><p>以下示例是基于 <code>click</code> 的 <code>calc</code> 程序：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> click</span><br><span class="line"></span><br><span class="line">sys.argv = [<span class="string">'calc'</span>, <span class="string">'echo'</span>, <span class="string">'--upper'</span>, <span class="string">'Hello, World'</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@click.group(help='Calculator Program.')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">cli</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 定义参数</span></span><br><span class="line"><span class="meta">@cli.command(name='echo', help='Echo input text in multiple forms')</span></span><br><span class="line"><span class="meta">@click.argument('text')</span></span><br><span class="line"><span class="meta">@click.option('--lower', is_flag=True, help='Lower input text')</span></span><br><span class="line"><span class="meta">@click.option('--upper', is_flag=True, help='Upper input text')</span></span><br><span class="line"><span class="comment"># 1. 业务逻辑</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">echo_text</span><span class="params">(text, lower, upper)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> lower:</span><br><span class="line">        print(text.lower())</span><br><span class="line">    <span class="keyword">elif</span> upper:</span><br><span class="line">        print(text.upper())</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        print(text)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@cli.command(name='eval', help='Eval input expression and return result')</span></span><br><span class="line"><span class="meta">@click.argument('expression')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">eval_expression</span><span class="params">(expression)</span>:</span></span><br><span class="line">    print(eval(expression))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">cli()</span><br></pre></td></tr></table></figure><p>从上述示例可以看到，元信息定义和处理逻辑无缝绑定在一起，能够直观地看出对应的参数会如何处理，这个优势在有大量参数需要处理时显得尤为突出。在处理函数中，接收到不再是像 <code>argparse</code> 或 <code>docopt</code> 中的一个包含所有参数的变量，而是具体的参数变量，这让处理逻辑在参数使用上也变得更加简便。</p><p>此外，<code>click</code> 还内置了很多实用工具和增强能力，如参数自动补全、分页支持、颜色、进度条等功能，能够有效提升开发效率。</p><h2 id="fire"><a href="#fire" class="headerlink" title="fire"></a>fire</h2><p>虽然前面三个库已经足够强大，但是仍然会有人认为不够简单。是否还有进一步简化的空间呢？如果只是定义函数，是否能让框架推测出参数元信息呢？理论上还真是可以。</p><p><a href="https://github.com/google/python-fire" target="_blank" rel="noopener">fire</a> 用一种面向广义对象的方式来玩转命令行，这种对象可以是类、函数、字典、列表等，它更加灵活，也更加简单。你都不需要定义参数类型，<code>fire</code> 会根据输入和参数默认值来自动判断，这无疑进一步简化了实现过程。</p><p>在 <code>fire</code> 的世界中，定义 Python 对象就够了。</p><p>以下示例是基于 <code>fire</code> 的 <code>calc</code> 程序：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line">sys.argv = [<span class="string">'calc'</span>, <span class="string">'echo'</span>, <span class="string">'"Hello, World"'</span>, <span class="string">'--upper'</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 业务逻辑</span></span><br><span class="line"><span class="comment"># 类中有几个方法，就意味着命令行程序有几个同名命令</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Calc</span>:</span></span><br><span class="line">    <span class="comment"># text 没有任何默认值，视为位置参数</span></span><br><span class="line">    <span class="comment"># lower/upper 有布尔类型的默认值，视为选项参数 --lower/--upper，</span></span><br><span class="line">    <span class="comment"># 且指定了为 True，不指定 False</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">echo</span><span class="params">(self, text, lower=False, upper=False)</span>:</span></span><br><span class="line">        <span class="string">"""Echo input text in multiple forms"""</span></span><br><span class="line">        <span class="keyword">if</span> lower:</span><br><span class="line">            print(text.lower())</span><br><span class="line">        <span class="keyword">elif</span> upper:</span><br><span class="line">            print(text.upper())</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            print(text)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">eval</span><span class="params">(self, expression)</span>:</span></span><br><span class="line">        <span class="string">"""Eval input expression and return result"""</span></span><br><span class="line">        print(eval(expression))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">fire.Fire(Calc)</span><br></pre></td></tr></table></figure><p>从上面的示例可以看出，使用 <code>fire</code> 足够的简单，一切都是根据约定来进行推断，包括支持哪些命令，每个命令接受的什么参数和选项。这种方式可以说是足够的 Pythonic，相比于 <code>click</code>，<code>fire</code> 把命令行参数的定义和函数参数的定义融为了一体。通过它，我们真的就只用关注业务逻辑。</p><p>不过简单往往也意味着对于复杂需求的捉襟见肘。仅仅通过默认值来推导命令行参数所能表达的情况是有限的，比如互斥选项、位置参数的类型限定都无法通过框架来表达，而只能由业务逻辑去判断。</p><h2 id="typer"><a href="#typer" class="headerlink" title="typer"></a>typer</h2><p>那么该如何在保持像 <code>fire</code> 这样简单实现的方式下，增强参数元信息的表达能力呢？既然默认参数的能力有限，那么如果使用 Python 3 的类型注解呢？</p><p><a href="https://typer.tiangolo.com/" target="_blank" rel="noopener">typer</a> 站在 <code>click</code> 巨人的肩膀上，借助 Python 3 类型注解的特性，既满足了简单直观编写的需要，又达到了应对复杂场景的目的，可谓是现代化的命令行库。</p><p>在 <code>typer</code> 的世界中，也是直接编写业务逻辑，和 <code>fire</code> 稍稍不同的点是使用了类型注解和默认值来表达参数元信息定义。</p><p>以下示例是基于 <code>typer</code> 的 <code>calc</code> 程序：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> typer</span><br><span class="line"></span><br><span class="line">sys.argv = [<span class="string">'calc'</span>, <span class="string">'echo'</span>, <span class="string">'"Hello, World"'</span>, <span class="string">'--upper'</span>]</span><br><span class="line">cli = typer.Typer(help=<span class="string">'Calculator Program.'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义命令 echo，及处理函数</span></span><br><span class="line"><span class="comment"># text 无默认值，视为位置参数，类型为字符串</span></span><br><span class="line"><span class="comment"># lower/upper 类型为 bool，默认值为 False，视为选项 --lower/--upper，</span></span><br><span class="line"><span class="comment"># 且指定了为 True，不指定 False</span></span><br><span class="line"><span class="meta">@cli.command(name='echo')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">echo_text</span><span class="params">(text: str, lower: bool = False, upper: bool = False)</span>:</span></span><br><span class="line">    <span class="string">"""Echo input text in multiple forms"""</span></span><br><span class="line">    <span class="keyword">if</span> lower:</span><br><span class="line">        print(text.lower())</span><br><span class="line">    <span class="keyword">elif</span> upper:</span><br><span class="line">        print(text.upper())</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        print(text)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义命令 eval，及处理函数</span></span><br><span class="line"><span class="comment"># expression 无默认值，视为位置参数，类型为字符串</span></span><br><span class="line"><span class="meta">@cli.command(name='eval')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">eval_expression</span><span class="params">(expression: str)</span>:</span></span><br><span class="line">    <span class="string">"""Eval input expression and return result"""</span></span><br><span class="line">    print(eval(expression))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">cli()</span><br></pre></td></tr></table></figure><p>从上面的示例可以看出，相比于 <code>click</code>，它免去了参数元信息的繁琐定义，取而代之的是类型注解；相比于 <code>fire</code>，它的元信息定义能力则大大增强，可以通过指定默认值为 <code>typer.Option</code> 或 <code>typer.Argument</code> 来进一步扩展参数和选项的语义。可以说是，<code>typer</code> 达到了简单与灵活的完美平衡。</p><h2 id="横向对比"><a href="#横向对比" class="headerlink" title="横向对比"></a>横向对比</h2><p>最后，我们横向对比下 <code>argparse</code>、<code>docopt</code>、<code>click</code>、<code>fire</code>、<code>typer</code> 库的各项功能和特点：</p><table><thead><tr><th></th><th style="text-align:left">argpase</th><th style="text-align:left">docopt</th><th style="text-align:left">click</th><th style="text-align:left">fire</th><th style="text-align:left">typer</th></tr></thead><tbody><tr><td>使用步骤数</td><td style="text-align:left">4 步</td><td style="text-align:left">3 步</td><td style="text-align:left">2 步</td><td style="text-align:left">1 步</td><td style="text-align:left">1 步</td></tr><tr><td>使用步骤数</td><td style="text-align:left">1. 设置解析器<br>2. 定义参数<br>3. 解析命令行<br>4. 业务逻辑</td><td style="text-align:left">1. 定义接口描述<br>2. 解析命令行<br>3. 业务逻辑</td><td style="text-align:left">1. 业务逻辑<br>2. 定义参数</td><td style="text-align:left">1. 业务逻辑</td><td style="text-align:left">1 . 业务逻辑</td></tr><tr><td>选项参数<br>（如 <code>--sum</code>）</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>位置参数<br>（如 <code>X Y</code>）</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>参数默认值<br></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>互斥选项<br>（如 <code>--car</code> 和 <code>--bus</code> 只能二选一）</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="yellow">✔</font><br>可通过第三方库支持</td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="red">✘</font></td></tr><tr><td>可变参数<br>（如指定多个 <code>--file</code>）</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>嵌套/父子命令<br></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>工具箱<br></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>链式命令调用<br></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="red">✘</font></td></tr><tr><td>类型约束</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td></tr></tbody></table><p>Python 的命令行库种类繁多、各具特色，它们并非是重复造轮子的产物，其背后的思想值得学习。结合横向对比的总结，可以选择出符合使用场景的库。如果几个库都符合，那么就选择你所偏爱的风格。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ul><li><a href="/2019/08/13/Python-命令行之旅：初探-argparse/" title="Python 命令行之旅：初探 argparse">Python 命令行之旅：初探 argparse</a></li><li><a href="/2019/08/20/Python-命令行之旅：深入-argparse（一）/" title="Python 命令行之旅：深入 argparse（一）">Python 命令行之旅：深入 argparse（一）</a></li><li><a href="/2019/08/27/Python-命令行之旅：深入-argparse（二）/" title="Python 命令行之旅：深入 argparse（二）">Python 命令行之旅：深入 argparse（二）</a></li><li><a href="/2019/09/04/Python-命令行之旅：使用-argparse-实现-git-命令/" title="Python 命令行之旅：使用 argparse 实现 git 命令">Python 命令行之旅：使用 argparse 实现 git 命令</a></li><li><a href="/2019/10/09/Python-命令行之旅：初探-docopt/" title="Python 命令行之旅：初探 docopt">Python 命令行之旅：初探 docopt</a></li><li><a href="/2019/10/15/Python-命令行之旅：深入-docopt/" title="Python 命令行之旅：深入 docopt">Python 命令行之旅：深入 docopt</a></li><li><a href="/2019/10/30/Python-命令行之旅：使用-docopt-实现-git-命令/" title="Python 命令行之旅：使用 docopt 实现 git 命令">Python 命令行之旅：使用 docopt 实现 git 命令</a></li><li><a href="/2019/11/04/Python-命令行之旅：初探-click/" title="Python 命令行之旅：初探 click">Python 命令行之旅：初探 click</a></li><li><a href="/2019/11/11/Python-命令行之旅：深入-click（一）/" title="Python 命令行之旅：深入 click（一）">Python 命令行之旅：深入 click（一）</a></li><li><a href="/2019/11/17/Python-命令行之旅：深入-click（二）/" title="Python 命令行之旅：深入 click（二）">Python 命令行之旅：深入 click（二）</a></li><li><a href="/2019/11/25/Python-命令行之旅：深入-click（三）/" title="Python 命令行之旅：深入 click（三）">Python 命令行之旅：深入 click（三）</a></li><li><a href="/2019/12/09/Python-命令行之旅：深入-click（四）/" title="Python 命令行之旅：深入 click（四）">Python 命令行之旅：深入 click（四）</a></li><li><a href="/2019/12/14/Python-命令行之旅：使用-click-实现-git-命令/" title="Python 命令行之旅：使用 click 实现 git 命令">Python 命令行之旅：使用 click 实现 git 命令</a></li><li><a href="/2019/12/22/Python-命令行之旅：初探-fire/" title="Python 命令行之旅：初探 fire">Python 命令行之旅：初探 fire</a></li><li><a href="/2019/12/29/Python-命令行之旅：深入-fire（一）/" title="Python 命令行之旅：深入 fire（一）">Python 命令行之旅：深入 fire（一）</a></li><li><a href="/2020/01/05/Python-命令行之旅：深入-fire（二）/" title="Python 命令行之旅：深入 fire（二）">Python 命令行之旅：深入 fire（二）</a></li><li><a href="/2020/01/12/Python-命令行之旅：使用-fire-实现-git-命令/" title="Python 命令行之旅：使用 fire 实现 git 命令">Python 命令行之旅：使用 fire 实现 git 命令</a></li><li><a href="/2020/02/09/Python-命令行之旅：argparse、docopt、click-和-fire-总结篇/" title="Python 命令行之旅：argparse、docopt、click 和 fire 总结篇">Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</a></li><li><a href="/2020/07/02/Python-命令行大乱斗/" title="Python 命令行大乱斗">Python 命令行大乱斗</a></li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文首发于 &lt;a href=&quot;https://mp.weixin.qq.com/s/szn0qm8_u6GTbVoW9GBP3Q&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;凌云时刻公众号&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;当你想实现一个命令行程序时，或许第一个想到的是用 Python 来实现。比如 CentOS 上大名鼎鼎的包管理工具 &lt;code&gt;yum&lt;/code&gt; 就是基于 Python 实现的。&lt;/p&gt;
&lt;p&gt;而 Python 的世界中有很多命令行库，每个库都各具特色。但我们往往不知道其背后的设计理念，也因此在选择时感到迷茫。这些库的作者为何在重复造轮子，他是从哪个角度来考虑，来让命令行库“演变”到一个新的更好用的形态。&lt;/p&gt;
&lt;p&gt;为了能够更加直观地感受到命令行库的设计理念，在此之前，我们不妨设计一个名为 &lt;code&gt;calc&lt;/code&gt; 的命令行程序，它能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持 &lt;code&gt;echo&lt;/code&gt; 子命令，对输入的字符串做处理来输出&lt;ul&gt;
&lt;li&gt;若不提供任何选项，则输出原始内容&lt;/li&gt;
&lt;li&gt;若提供 &lt;code&gt;--lower&lt;/code&gt; 选项，则输出小写字符串&lt;/li&gt;
&lt;li&gt;若提供 &lt;code&gt;--upper&lt;/code&gt; 选项，则输出大写字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;支持 &lt;code&gt;eval&lt;/code&gt; 子命令，针对输入调用 Python 的 &lt;code&gt;eval&lt;/code&gt; 函数，将结果输出（作为示例，我们不考虑安全性问题）&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/categories/Python/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
      <category term="argparse" scheme="http://prodesire.cn/tags/argparse/"/>
    
      <category term="click" scheme="http://prodesire.cn/tags/click/"/>
    
      <category term="docopt" scheme="http://prodesire.cn/tags/docopt/"/>
    
      <category term="fire" scheme="http://prodesire.cn/tags/fire/"/>
    
      <category term="typer" scheme="http://prodesire.cn/tags/typer/"/>
    
  </entry>
  
  <entry>
    <title>限流后，你可以通过指数退避来重试</title>
    <link href="http://prodesire.cn/2020/03/04/%E9%99%90%E6%B5%81%E5%90%8E%EF%BC%8C%E4%BD%A0%E5%8F%AF%E4%BB%A5%E9%80%9A%E8%BF%87%E6%8C%87%E6%95%B0%E8%A1%A5%E5%81%BF%E6%9D%A5%E9%87%8D%E8%AF%95/"/>
    <id>http://prodesire.cn/2020/03/04/%E9%99%90%E6%B5%81%E5%90%8E%EF%BC%8C%E4%BD%A0%E5%8F%AF%E4%BB%A5%E9%80%9A%E8%BF%87%E6%8C%87%E6%95%B0%E8%A1%A5%E5%81%BF%E6%9D%A5%E9%87%8D%E8%AF%95/</id>
    <published>2020-03-04T12:56:13.000Z</published>
    <updated>2020-07-24T15:00:04.384Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h2><p>最近做云服务 API 测试项目的过程中，发现某些时候会大批量调用 API，从而导致限流的报错。在遇到这种报错时，传统的重试策略是每隔一段时间重试一次。但由于是固定的时间重试一次，重试时又会有大量的请求在同一时刻涌入，会不断地造成限流。</p><p>这让我回想起两年前在查阅<a href="http://docs.celeryproject.org/en/latest/userguide/tasks.html?highlight=exponential#Task.retry_backoff" title="Celery Task文档" target="_blank" rel="noopener">Celery Task 文档</a>的时候发现可以为任务设置 <code>retry_backoff</code> 的经历，它让任务在失败时以 <code>指数退避</code> 的方式进行重试。那么指数退避究竟是什么样的呢？</p><h2 id="二、指数退避"><a href="#二、指数退避" class="headerlink" title="二、指数退避"></a>二、指数退避</h2><p>根据 wiki 上对 <a href="https://en.wikipedia.org/wiki/Exponential_backoff" title="Exponential backoff" target="_blank" rel="noopener">Exponential backoff</a> 的说明，指数退避是一种通过反馈，成倍地降低某个过程的速率，以逐渐找到合适速率的算法。</p><p>在以太网中，该算法通常用于冲突后的调度重传。根据时隙和重传尝试次数来决定延迟重传。</p><p>在 <code>c</code> 次碰撞后（比如请求失败），会选择 0 和 $2^c-1$ 之间的随机值作为时隙的数量。</p><ul><li>对于第 1 次碰撞来说，每个发送者将会等待 0 或 1 个时隙进行发送。</li><li>而在第 2 次碰撞后，发送者将会等待 0 到 3（ 由 $2^2-1$ 计算得到）个时隙进行发送。</li><li>而在第 3 次碰撞后，发送者将会等待 0 到 7（ 由 $2^3-1$ 计算得到）个时隙进行发送。</li><li>以此类推……</li></ul><p>随着重传次数的增加，延迟的程度也会指数增长。</p><p>说的通俗点，每次重试的时间间隔都是上一次的两倍。</p><h2 id="三、指数退避的期望值"><a href="#三、指数退避的期望值" class="headerlink" title="三、指数退避的期望值"></a>三、指数退避的期望值</h2><p>考虑到退避时间的均匀分布，退避时间的数学期望是所有可能性的平均值。也就是说，在 <code>c</code> 次冲突之后，退避时隙数量在 <code>[0,1，...，N]</code> 中，其中 $N=2^c-1$ ，则退避时间的数学期望（以时隙为单位）是</p><p>$$E(c)=\frac{1}{N+1}\sum_{i=0}^{N}{i}=\frac{1}{N+1}\frac{N(N+1)}{2}=\frac{N}{2}=\frac{2^c-1}{2}$$</p><p>那么对于前面讲到的例子来说：</p><ul><li>第 1 次碰撞后，退避时间期望为 $E(1)=\frac{2^1-1}{2}=0.5$</li><li>第 2 次碰撞后，退避时间期望为 $E(2)=\frac{2^2-1}{2}=1.5$</li><li>第 3 次碰撞后，退避时间期望为 $E(3)=\frac{2^3-1}{2}=3.5$</li></ul><h2 id="四、指数退避的应用"><a href="#四、指数退避的应用" class="headerlink" title="四、指数退避的应用"></a>四、指数退避的应用</h2><h3 id="4-1-Celery-中的指数退避算法"><a href="#4-1-Celery-中的指数退避算法" class="headerlink" title="4.1 Celery 中的指数退避算法"></a>4.1 Celery 中的指数退避算法</h3><p>来看下 <a href="https://github.com/celery/celery/blob/v4.3.0/celery/utils/time.py#L392" title="celery/utils/time.py" target="_blank" rel="noopener">celery/utils/time.py</a> 中获取指数退避时间的函数：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_exponential_backoff_interval</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    factor,</span></span></span><br><span class="line"><span class="function"><span class="params">    retries,</span></span></span><br><span class="line"><span class="function"><span class="params">    maximum,</span></span></span><br><span class="line"><span class="function"><span class="params">    full_jitter=False</span></span></span><br><span class="line"><span class="function"><span class="params">)</span>:</span></span><br><span class="line">    <span class="string">"""Calculate the exponential backoff wait time."""</span></span><br><span class="line">    <span class="comment"># Will be zero if factor equals 0</span></span><br><span class="line">    countdown = factor * (<span class="number">2</span> ** retries)</span><br><span class="line">    <span class="comment"># Full jitter according to</span></span><br><span class="line">    <span class="comment"># https://www.awsarchitectureblog.com/2015/03/backoff.html</span></span><br><span class="line">    <span class="keyword">if</span> full_jitter:</span><br><span class="line">        countdown = random.randrange(countdown + <span class="number">1</span>)</span><br><span class="line">    <span class="comment"># Adjust according to maximum wait time and account for negative values.</span></span><br><span class="line">    <span class="keyword">return</span> max(<span class="number">0</span>, min(maximum, countdown))</span><br></pre></td></tr></table></figure><p>这里 <code>factor</code> 是退避系数，作用于整体的退避时间。而 <code>retries</code> 则对应于上文的 <code>c</code>（也就是碰撞次数）。核心内容 <code>countdown = factor * (2 ** retries)</code> 和上文提到的指数退避算法思路一致。<br>在此基础上，可以将 <code>full_jitter</code> 设置为 <code>True</code>，含义是对退避时间做一个“抖动”，以具有一定的随机性。最后呢，则是限定给定值不能超过最大值 <code>maximum</code>，以避免无限长的等待时间。不过一旦取最大的退避时间，也就可能导致多个任务同时再次执行。更多见 <a href="http://docs.celeryproject.org/en/latest/userguide/tasks.html#Task.retry_jitter" title="Task.retry_jitter" target="_blank" rel="noopener">Task.retry_jitter</a> 。</p><h3 id="4-2-《UNIX-环境高级编程》中的连接示例"><a href="#4-2-《UNIX-环境高级编程》中的连接示例" class="headerlink" title="4.2 《UNIX 环境高级编程》中的连接示例"></a>4.2 《UNIX 环境高级编程》中的连接示例</h3><p>在 《UNIX 环境高级编程》（第 3 版）的 16.4 章节中，也有一个使用指数退避来建立连接的示例：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"apue.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/socket.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> MAXSLEEP 128</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">connect_retry</span><span class="params">(<span class="keyword">int</span> domain, <span class="keyword">int</span> type, <span class="keyword">int</span> protocol,</span></span></span><br><span class="line"><span class="function"><span class="params">                  <span class="keyword">const</span> struct sockaddr *addr, <span class="keyword">socklen_t</span> alen)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> numsec, fd;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    * 使用指数退避尝试连接</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">for</span> (numsec = <span class="number">1</span>; numsec &lt; MAXSLEEP; numsec &lt;&lt;= <span class="number">1</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (fd = socket(domain, type, protocol) &lt; <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">if</span> (connect(fd, addr, alen) == <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">/*</span></span><br><span class="line"><span class="comment">            * 连接接受</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">            <span class="keyword">return</span> (fd);</span><br><span class="line">        &#125;</span><br><span class="line">        close(fd);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">        * 延迟后重试</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">        <span class="keyword">if</span> (numsec &lt;= MAXSLEEP / <span class="number">2</span>)</span><br><span class="line">            sleep(numsec);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果连接失败，进程会休眠一小段时间（<code>numsec</code>），然后进入下次循环再次尝试。每次循环休眠时间是上一次的 2 倍，直到最大延迟 1 分多钟，之后便不再重试。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>回到开头的问题，在遇到限流错误的时候，通过指数退避算法进行重试，我们可以最大程度地避免再次限流。相比于固定时间重试，指数退避加入了时间放大性和随机性，从而变得更加“智能”。至此，我们再也不用担心限流让整个测试程序运行中断了~</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;一、背景&quot;&gt;&lt;a href=&quot;#一、背景&quot; class=&quot;headerlink&quot; title=&quot;一、背景&quot;&gt;&lt;/a&gt;一、背景&lt;/h2&gt;&lt;p&gt;最近做云服务 API 测试项目的过程中，发现某些时候会大批量调用 API，从而导致限流的报错。在遇到这种报错时，传统的重试策
      
    
    </summary>
    
    
    
      <category term="限流" scheme="http://prodesire.cn/tags/%E9%99%90%E6%B5%81/"/>
    
      <category term="指数退避" scheme="http://prodesire.cn/tags/%E6%8C%87%E6%95%B0%E9%80%80%E9%81%BF/"/>
    
  </entry>
  
  <entry>
    <title>PEP 584：字典合并操作符来了</title>
    <link href="http://prodesire.cn/2020/02/29/PEP-584%EF%BC%9A%E5%AD%97%E5%85%B8%E5%90%88%E5%B9%B6%E6%93%8D%E4%BD%9C%E7%AC%A6%E6%9D%A5%E4%BA%86/"/>
    <id>http://prodesire.cn/2020/02/29/PEP-584%EF%BC%9A%E5%AD%97%E5%85%B8%E5%90%88%E5%B9%B6%E6%93%8D%E4%BD%9C%E7%AC%A6%E6%9D%A5%E4%BA%86/</id>
    <published>2020-02-29T03:20:32.000Z</published>
    <updated>2020-07-24T15:00:04.356Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>就在本周，字典合并特性（<a href="https://www.python.org/dev/peps/pep-0584/" title="PEP584" target="_blank" rel="noopener">PEP 584</a>）的提交被合入了 CPython 的主干分支，并在 2020-02-26 发布了 <a href="https://www.python.org/downloads/release/python-390a4/" title="Python 3.9.0a4" target="_blank" rel="noopener">Python 3.9.0a4</a> 预览版本。</p><img src="/2020/02/29/PEP-584：字典合并操作符来了/1.png"><p>那什么是字典合并操作符呢？在回答这个问题前，我们不妨回忆下集合的合并操作。当我们想要对两个结合做合并操作时，会怎么做呢？</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>s1 = &#123;<span class="number">1</span>, <span class="number">2</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>s2 = &#123;<span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>s1 | s2  <span class="comment"># s1 和 s2 取并集，生成新的集合；与 s1.union(s2) 等价</span></span><br><span class="line">&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>s1 |= s2 <span class="comment"># s1 和 s2 取并集，并更新到 s1 上；与 s1.update(s2) 等价</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>s1</span><br><span class="line">&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br></pre></td></tr></table></figure><p>类似地，我们希望 Python 中的字典能像集合一样，使用 <code>|</code> 和 <code>|=</code> 作为合并操作符，以解决我们在过去合并字典时感受到的“痛苦”，于是就有了 <code>PEP 584</code>。</p><p>今天就想和大家聊聊这个提案，不仅是要了解字典合并操作符的前世今生，更是要学习提案作者以及参与者是如何对引入一个新特性的思考，辩证性地分析利弊，最终确定引入。最后还想和大家分享下在 CPython 层面是如何实现的。</p><a id="more"></a><h2 id="二、背景"><a href="#二、背景" class="headerlink" title="二、背景"></a>二、背景</h2><p>在平时使用 Python 的过程中，我们有时会需要合并字典。目前合并字典有多种方式，它们或多或少都有些缺点。</p><h3 id="2-1-dict-update"><a href="#2-1-dict-update" class="headerlink" title="2.1 dict.update"></a>2.1 dict.update</h3><p><code>d1.update(d2)</code> 确实能合并两个字典，但它是在修改<code>d1</code>的基础上进行。如果我们想要合并成一个新的字典，没有一个直接使用表达式的方式，而需要借助临时变量进行：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">e = d1.copy()</span><br><span class="line">e.update(d2)</span><br></pre></td></tr></table></figure><h3 id="2-2-d1-d2"><a href="#2-2-d1-d2" class="headerlink" title="2.2 {d1, d2}"></a>2.2 {<strong>d1, </strong>d2}</h3><p>字典解包可以将两个字典合并为一个新的字典，但看起来有些丑陋，并且不能让人显而易见地看出这是在合并字典。</p><p><code>{**d1, **d2}</code> 还会忽略映射类型，并始终返回字典类型。</p><h3 id="2-3-collections-ChainMap"><a href="#2-3-collections-ChainMap" class="headerlink" title="2.3 collections.ChainMap"></a>2.3 collections.ChainMap</h3><p><code>ChainMap</code> 很少有人知道，它也可以用作合并字典。但和前面合并方式相反，在合并两个字典时，第一个字典的键会覆盖第二个字典的相同键。</p><p>此外，由于 <code>ChainMap</code> 是对入参字典的封装，这意味着写入 <code>ChainMap</code> 会修改原始字典：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><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> ChainMap</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d1 = &#123;<span class="string">'a'</span>:<span class="number">1</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d2 = &#123;<span class="string">'a'</span>:<span class="number">2</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>merged = ChainMap(d1, d2)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>merged[<span class="string">'a'</span>]     <span class="comment"># d1['a'] 会覆盖 d2['a']</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>merged[<span class="string">'a'</span>] = <span class="number">3</span> <span class="comment"># 实际等同于 d1['a'] = 3</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d1</span><br><span class="line">&#123;<span class="string">'a'</span>: <span class="number">3</span>&#125;</span><br></pre></td></tr></table></figure><h3 id="2-4-dict-d1-d2"><a href="#2-4-dict-d1-d2" class="headerlink" title="2.4 dict(d1, **d2)"></a>2.4 dict(d1, **d2)</h3><p>这是一种鲜为人知的合并字典的“巧妙方法”，但如果字典的键不是字符串，它就不能有效工作了：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>d1 = &#123;<span class="string">'a'</span>: <span class="number">1</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d2 = &#123;<span class="number">2</span>: <span class="number">2</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>dict(d1, **d2)</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  ...</span><br><span class="line">TypeError: keywords must be strings</span><br></pre></td></tr></table></figure><h2 id="三、原理"><a href="#三、原理" class="headerlink" title="三、原理"></a>三、原理</h2><p>新操作符同 <code>dict.update</code> 方法的关系，就和列表连接（<code>+</code>）、扩展（<code>+=</code>）操作符同 <code>list.extend</code> 方法的关系一样。需要注意的是，这和集合中 <code>|</code>/<code>|=</code> 操作符同 <code>set.update</code> 的关系稍有不同。作者明确了允许就地运算符接受更广泛的类型（就像 <code>list</code> 那样）是一种更有用的设计，并且限制二进制操作符的操作数类型（就像 <code>list</code> 那样）将有助于避免由复杂的隐式类型转换引起的错误被吞掉。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>l1 = [<span class="number">1</span>, <span class="number">2</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>l1 + (<span class="number">3</span>,) <span class="comment"># 限制操作数的类型，不是列表就报错</span></span><br><span class="line">Traceback (most recent call last)</span><br><span class="line">...</span><br><span class="line">TypeError: can only concatenate list (<span class="keyword">not</span> <span class="string">"tuple"</span>) to list</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>l1 += (<span class="number">3</span>,) <span class="comment"># 允许就地运算符接受更广泛的类型（如元组）</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>l1</span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br></pre></td></tr></table></figure><p>当合并字典发生键冲突时，以最右边的值为准。这和现存的字典类似操作相符，比如：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;<span class="string">'a'</span>: <span class="number">1</span>, <span class="string">'a'</span>: <span class="number">2</span>&#125; <span class="comment"># 2 覆盖 1</span></span><br><span class="line">&#123;**d, **e&#125;       <span class="comment"># e覆盖d中相同键所对应的值</span></span><br><span class="line">d.update(e)      <span class="comment"># e覆盖d中相同键所对应的值</span></span><br><span class="line">d[k] = v         <span class="comment"># v 覆盖原有值</span></span><br><span class="line">&#123;k: v <span class="keyword">for</span> x <span class="keyword">in</span> (d, e) <span class="keyword">for</span> (k, v) <span class="keyword">in</span> x.items()&#125; <span class="comment"># e覆盖d中相同键所对应的值</span></span><br></pre></td></tr></table></figure><h2 id="四、规范"><a href="#四、规范" class="headerlink" title="四、规范"></a>四、规范</h2><p>字典合并会返回一个新字典，该字典由左操作数与右操作数合并而成，每个操作数必须是 <code>dict</code>（或 <code>dict</code> 子类的实例）。如果两个操作数中都出现一个键，则最后出现的值（即来自右侧操作数的值）将会覆盖：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>d = &#123;<span class="string">'spam'</span>: <span class="number">1</span>, <span class="string">'eggs'</span>: <span class="number">2</span>, <span class="string">'cheese'</span>: <span class="number">3</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>e = &#123;<span class="string">'cheese'</span>: <span class="string">'cheddar'</span>, <span class="string">'aardvark'</span>: <span class="string">'Ethel'</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d | e</span><br><span class="line">&#123;<span class="string">'spam'</span>: <span class="number">1</span>, <span class="string">'eggs'</span>: <span class="number">2</span>, <span class="string">'cheese'</span>: <span class="string">'cheddar'</span>, <span class="string">'aardvark'</span>: <span class="string">'Ethel'</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>e | d <span class="comment"># 不符合交换律，左右互换操作数会得到不同的结果</span></span><br><span class="line">&#123;<span class="string">'aardvark'</span>: <span class="string">'Ethel'</span>, <span class="string">'spam'</span>: <span class="number">1</span>, <span class="string">'eggs'</span>: <span class="number">2</span>, <span class="string">'cheese'</span>: <span class="number">3</span>&#125;</span><br></pre></td></tr></table></figure><p>扩展赋值版本的就地操作：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>d |= e <span class="comment"># 将 e 更新到 d 中</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d</span><br><span class="line">&#123;<span class="string">'spam'</span>: <span class="number">1</span>, <span class="string">'eggs'</span>: <span class="number">2</span>, <span class="string">'cheese'</span>: <span class="string">'cheddar'</span>, <span class="string">'aardvark'</span>: <span class="string">'Ethel'</span>&#125;</span><br></pre></td></tr></table></figure><p>扩展赋值的行为和字典的 <code>update</code> 方法完全一样，它还支持任何实现了映射协议（更确切地说是实现了 <code>keys</code> 和 <code>__getitem__</code> 方法）或键值对迭代对象。所以：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>d | [(<span class="string">'spam'</span>, <span class="number">999</span>)]   <span class="comment"># “原理”章节中提到限制操作数的类型，不是字典或字典子类就报错</span></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  ...</span><br><span class="line">TypeError: can only merge dict (<span class="keyword">not</span> <span class="string">"list"</span>) to dict</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d |= [(<span class="string">'spam'</span>, <span class="number">999</span>)]  <span class="comment"># “原理”章节中提到允许就地运算符接受更广泛的类型，其行为和 update 一样，接受键值对迭代对象</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d</span><br><span class="line">&#123;<span class="string">'eggs'</span>: <span class="number">2</span>, <span class="string">'cheese'</span>: <span class="string">'cheddar'</span>, <span class="string">'aardvark'</span>: <span class="string">'Ethel'</span>, <span class="string">'spam'</span>: <span class="number">999</span>&#125;</span><br></pre></td></tr></table></figure><h2 id="五、主流观点"><a href="#五、主流观点" class="headerlink" title="五、主流观点"></a>五、主流观点</h2><h3 id="5-1-字典合并不符合交换律"><a href="#5-1-字典合并不符合交换律" class="headerlink" title="5.1 字典合并不符合交换律"></a>5.1 字典合并不符合交换律</h3><p>合并是符合交换律的，但是字典联合却没有（<code>d | e != e | d</code>）。</p><blockquote><p>回应</p></blockquote><p>Python 中有过不符合交换律的合并先例：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>&#123;<span class="number">0</span>&#125; | &#123;<span class="keyword">False</span>&#125;</span><br><span class="line">&#123;<span class="number">0</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>&#123;<span class="keyword">False</span>&#125; | &#123;<span class="number">0</span>&#125;</span><br><span class="line">&#123;<span class="keyword">False</span>&#125;</span><br></pre></td></tr></table></figure><p>上述结果虽然是相等的，但是本质是不同的。通常来说，<code>a | b</code> 和 <code>b | a</code> 并不相同。</p><h3 id="5-2-字典合并并不高效"><a href="#5-2-字典合并并不高效" class="headerlink" title="5.2 字典合并并不高效"></a>5.2 字典合并并不高效</h3><p>类似管道写法使用多次字典合并并不高效，比如 <code>d | e | f | g | h</code> 会创建和销毁三个临时映射。</p><blockquote><p>回应</p></blockquote><p>这种问题在序列级联时同样会出现。</p><p>序列级联的每一次合并都会使序列中的元素总数增加，最终会带来 O(N^2) 的性能开销。而字典合并有可能会有重复键，因此临时映射的大小并不会如此快速地增长。</p><p>正如我们很少将大量的列表或元组连接在一起一样，PEP的作者任务合并大量的字典也是少见情况。若是确实有这样的诉求，那么最好使用显式的循环和就地合并：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">new = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> d <span class="keyword">in</span> many_dicts:</span><br><span class="line">    new |= d</span><br></pre></td></tr></table></figure><h3 id="5-3-字典合并是有损的"><a href="#5-3-字典合并是有损的" class="headerlink" title="5.3 字典合并是有损的"></a>5.3 字典合并是有损的</h3><p>字典合并可能会丢失数据（相同键的值可能消失），其他形式的合并并不会。</p><blockquote><p>回应</p></blockquote><p>作者并不觉得这种有损是一个问题。此外，<code>dict.update</code> 也会发生这种情况，但并不会丢弃键，这其实是符合预期的。只不过是现在使用的不是 <code>update</code> 而是 <code>|</code>。</p><p>如果从不可逆的角度考虑，其他类型的合并也是有损的。假设 <code>a | b</code> 的结果是365，那么 <code>a</code> 和 <code>b</code> 是多少却不得而知。</p><h3 id="5-4-只有一种方法达到目的"><a href="#5-4-只有一种方法达到目的" class="headerlink" title="5.4 只有一种方法达到目的"></a>5.4 只有一种方法达到目的</h3><p>字典合并不符合“Only One Way”的禅宗。</p><blockquote><p>回应</p></blockquote><p>其实并没有这样的禅宗。“Only One Way”起源于很早之前Perl社区对Python的诽谤。</p><h3 id="5-5-超过一种方法达到目的"><a href="#5-5-超过一种方法达到目的" class="headerlink" title="5.5 超过一种方法达到目的"></a>5.5 超过一种方法达到目的</h3><p>好吧，禅宗并没有说“Only One Way To Do It”。但是它明确禁止“超过一种方法达到目的”。</p><blockquote><p>回应</p></blockquote><p>并没有这样的禁止。Python 之禅仅表达了对“仅一种显而易见的方式”的偏爱。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">There should be one-- and preferably only one --obvious way to do</span><br><span class="line">it.</span><br></pre></td></tr></table></figure><p>它的重点是应该有一种明显的方式达到目的。对于字典更新操作来说，我们可能希望至少执行两个不同的操作：</p><ul><li>就地更新字典：显而易见的方式是使用 <code>update()</code> 方法。如果此提案被接受，<code>|=</code> 扩展赋值操作符也将等效，但这是扩展赋值如何定义的副作用。选择哪种取决于使用者口味。</li><li>合并两个现存的字典到新字典中：此提案中显而易见的方法是使用 <code>|</code> 合并操作符。</li></ul><p>实际上，Python 里经常违反对“仅一种方式”的偏爱。例如，每个 <code>for</code> 循环都可以重写为 <code>while</code> 循环；每个 <code>if</code> 块都可以写为 <code>if/else</code> 块。列表、集合和字典推导都可以用生成器表达式代替。列表提供了不少于五种方法来实现级联：</p><ul><li>级联操作符：<code>a + b</code></li><li>就地级联操作符：<code>a + = b</code></li><li>切片分配：<code>a[len(a):] = b</code></li><li>序列解压缩：<code>[*a, *b]</code></li><li>扩展方法：<code>a.extend(b)</code></li></ul><p>我们不能太教条主义，不能因为它违反了“仅一种方式”就非常严格的拒绝有用的功能。</p><h3 id="5-6-字典合并让代码更难理解"><a href="#5-6-字典合并让代码更难理解" class="headerlink" title="5.6 字典合并让代码更难理解"></a>5.6 字典合并让代码更难理解</h3><p>字典合并让人们更难理解代码的含义。为了解释该异议，而不是具体引用任何人的话：“在看到 <code>spam | eggs</code>，如果不知道 <code>spam</code> 和 <code>eggs</code> 是什么，根本就不知道这个表达式的作用”。</p><blockquote><p>回应</p></blockquote><p>这确实如此，即使没有该提案，<code>|</code> 操作符的现状也是如此：</p><ul><li>对于 <code>int</code>/<code>bool</code> 是按位或</li><li>对于 <code>set</code>/<code>forzenset</code> 是并集</li><li>还可能是任何其他的重载操作</li></ul><p>添加字典合并看起来并不会让理解代码变得更困难。确定 <code>spam</code> 和 <code>eggs</code> 是映射类型并不比确定是集合还是整数要花更多的工作。其实良好的命名约定将会有助于改善情况：<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">flags |= WRITEABLE  <span class="comment"># 可能就是数字的按位或</span></span><br><span class="line">DO_NOT_RUN = WEEKENDS | HOLIDAYS  <span class="comment"># 可能就是集合合并</span></span><br><span class="line">settings = DEFAULT_SETTINGS | user_settings | workspace_settings  <span class="comment"># 可能就是字典合并</span></span><br></pre></td></tr></table></figure></p><h3 id="5-7-参考下完整的集合API"><a href="#5-7-参考下完整的集合API" class="headerlink" title="5.7 参考下完整的集合API"></a>5.7 参考下完整的集合API</h3><p>字典和集合很相似，应该要支持集合所支持的操作符：<code>|</code>、<code>&amp;</code>、<code>^</code> 和 <code>-</code>。</p><blockquote><p>回应</p></blockquote><p>也许后续会有PEP来专门说明这些操作符如何用于字典。简单来说：</p><p>把集合的对称差集（^）操作用在字典上面是显而易见且自然。比如：<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>d1 = &#123;<span class="string">"spam"</span>: <span class="number">1</span>, <span class="string">"eggs"</span>: <span class="number">2</span>&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>d2 = &#123;<span class="string">"ham"</span>: <span class="number">3</span>, <span class="string">"eggs"</span>: <span class="number">4</span>&#125;</span><br></pre></td></tr></table></figure></p><p>对于 <code>d1</code> 和 <code>d2</code> 对称差集，我们期望 <code>d1 ^ d2</code> 应该是 <code>{&quot;spam&quot;: 1, &quot;ham&quot;: 3}</code></p><p>把集合的差集（-）操作用在字典上面也是显而易见和自然的。比如 <code>d1</code> 和 <code>d2</code> 的差集，我们期望：</p><ul><li><code>d1 - d2</code> 为 <code>{&quot;spam&quot;: 1}</code></li><li><code>d2 - d1</code> 为 <code>{&quot;ham&quot;: 3}</code></li></ul><p>把集合的交集（&amp;）操作用在字典上面就有些问题了。虽然很容易确定两个字典中键的交集，但是如何处理键所对应的值就比较模糊。不难看出 <code>d1</code> 和 <code>d2</code> 的共同键是 <code>eggs</code>，如果我们遵循“后者胜出”的一致性原则，那么值就是 4。</p><h2 id="六、已拒绝的观点"><a href="#六、已拒绝的观点" class="headerlink" title="六、已拒绝的观点"></a>六、已拒绝的观点</h2><p><code>PEP 584</code> 提案中罗列了很多已拒绝的观点，比如使用 <code>+</code> 来合并字典；在合并字典时也合并值类型为列表的值等等。这些观点都非常有意思，被拒绝的理由也同样有说服力。限于篇幅的原因不再进一步展开，感兴趣的可以阅读 <a href="https://www.python.org/dev/peps/pep-0584/#id34。" target="_blank" rel="noopener">https://www.python.org/dev/peps/pep-0584/#id34。</a></p><h2 id="七、实现"><a href="#七、实现" class="headerlink" title="七、实现"></a>七、实现</h2><h3 id="7-1-纯-Python-实现"><a href="#7-1-纯-Python-实现" class="headerlink" title="7.1 纯 Python 实现"></a>7.1 纯 Python 实现</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">__or__</span><span class="params">(self, other)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> isinstance(other, dict):</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">NotImplemented</span></span><br><span class="line">    new = dict(self)</span><br><span class="line">    new.update(other)</span><br><span class="line">    <span class="keyword">return</span> new</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">__ror__</span><span class="params">(self, other)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> isinstance(other, dict):</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">NotImplemented</span></span><br><span class="line">    new = dict(other)</span><br><span class="line">    new.update(self)</span><br><span class="line">    <span class="keyword">return</span> new</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">__ior__</span><span class="params">(self, other)</span>:</span></span><br><span class="line">    dict.update(self, other)</span><br><span class="line">    <span class="keyword">return</span> self</span><br></pre></td></tr></table></figure><p>纯 Python 实现并不复杂，我们只需让 dict 实现几个魔法方法：</p><ul><li><code>__or__</code> 和 <code>__ror__</code> 魔法方法对应于 <code>|</code> 操作符，<code>__or__</code> 表示对象在操作符左侧，<code>__ror__</code> 表示对象在操作符右侧。实现就是根据左侧操作数生成一个新字典，再把右侧操作数更新到新字典中，并返回新字典。</li><li><code>__ior__</code> 魔法方法对应于 <code>|=</code> 操作符，将右侧操作数更新到自身即可。</li></ul><h3 id="7-2-CPython-实现"><a href="#7-2-CPython-实现" class="headerlink" title="7.2 CPython 实现"></a>7.2 CPython 实现</h3><p>CPython 中字典合并的详细实现可见此 PR： <a href="https://github.com/python/cpython/pull/12088/files" target="_blank" rel="noopener">https://github.com/python/cpython/pull/12088/files</a> 。</p><p>最核心的实现如下：<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 实现字典合并生成新字典的逻辑，对应于 | 操作符</span></span><br><span class="line"><span class="keyword">static</span> PyObject *</span><br><span class="line">dict_or(PyObject *self, PyObject *other)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (!PyDict_Check(self) || !PyDict_Check(other)) &#123;</span><br><span class="line">        Py_RETURN_NOTIMPLEMENTED;</span><br><span class="line">    &#125;</span><br><span class="line">    PyObject *<span class="keyword">new</span> = PyDict_Copy(self);</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">new</span> == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (dict_update_arg(<span class="keyword">new</span>, other)) &#123;</span><br><span class="line">        Py_DECREF(<span class="keyword">new</span>); <span class="comment">// 减少引用计数</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现字典就地合并逻辑，对应于 |= 操作符</span></span><br><span class="line"><span class="keyword">static</span> PyObject *</span><br><span class="line">dict_ior(PyObject *self, PyObject *other)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (dict_update_arg(self, other)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    Py_INCREF(self); <span class="comment">// 增加引用计数</span></span><br><span class="line">    <span class="keyword">return</span> self;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>CPython 的实现逻辑和纯Python实现几乎一样，唯独需要注意的就是引用计数的问题，这关系到对象的垃圾回收。</p><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p><code>PEP 584</code> 是一个非常精彩的提案，引入 <code>|</code> 和 <code>|=</code> 操作符用作字典合并，看似是一个比较简单的功能，但所要考虑的情况却不少。不仅需要说明这个提案的背景，目前有哪些方式可以达到目的，它们有哪些痛点；还要考虑对既有类型引入操作符所带来的各种影响，对开发者提出的质疑和顾虑进行思考和解决。整个提案所涉及到的方法论、思考维度、知识点都非常值得学习。</p><p>对使用者来说，合并字典将会变得更加方便。在提案的最后，作者给出了许多第三方库在合并字典时采用新方式编写的例子，可谓是简洁了不少。详见 <a href="https://www.python.org/dev/peps/pep-0584/#id50" target="_blank" rel="noopener">https://www.python.org/dev/peps/pep-0584/#id50</a> 。</p><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;就在本周，字典合并特性（&lt;a href=&quot;https://www.python.org/dev/peps/pep-0584/&quot; title=&quot;PEP584&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PEP 584&lt;/a&gt;）的提交被合入了 CPython 的主干分支，并在 2020-02-26 发布了 &lt;a href=&quot;https://www.python.org/downloads/release/python-390a4/&quot; title=&quot;Python 3.9.0a4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Python 3.9.0a4&lt;/a&gt; 预览版本。&lt;/p&gt;
&lt;img src=&quot;/2020/02/29/PEP-584：字典合并操作符来了/1.png&quot;&gt;
&lt;p&gt;那什么是字典合并操作符呢？在回答这个问题前，我们不妨回忆下集合的合并操作。当我们想要对两个结合做合并操作时，会怎么做呢？&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;s1 = &amp;#123;&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;s2 = &amp;#123;&lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;s1 | s2  &lt;span class=&quot;comment&quot;&gt;# s1 和 s2 取并集，生成新的集合；与 s1.union(s2) 等价&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#123;&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;s1 |= s2 &lt;span class=&quot;comment&quot;&gt;# s1 和 s2 取并集，并更新到 s1 上；与 s1.update(s2) 等价&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;s1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#123;&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;类似地，我们希望 Python 中的字典能像集合一样，使用 &lt;code&gt;|&lt;/code&gt; 和 &lt;code&gt;|=&lt;/code&gt; 作为合并操作符，以解决我们在过去合并字典时感受到的“痛苦”，于是就有了 &lt;code&gt;PEP 584&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;今天就想和大家聊聊这个提案，不仅是要了解字典合并操作符的前世今生，更是要学习提案作者以及参与者是如何对引入一个新特性的思考，辩证性地分析利弊，最终确定引入。最后还想和大家分享下在 CPython 层面是如何实现的。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="PEP" scheme="http://prodesire.cn/categories/Python/PEP/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="PEP" scheme="http://prodesire.cn/tags/PEP/"/>
    
  </entry>
  
  <entry>
    <title>图表即代码：使用 Diagrams 制作云系统架构原型图</title>
    <link href="http://prodesire.cn/2020/02/16/%E5%9B%BE%E8%A1%A8%E5%8D%B3%E4%BB%A3%E7%A0%81%EF%BC%9A%E4%BD%BF%E7%94%A8-Diagrams-%E5%88%B6%E4%BD%9C%E4%BA%91%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E5%8E%9F%E5%9E%8B%E5%9B%BE/"/>
    <id>http://prodesire.cn/2020/02/16/%E5%9B%BE%E8%A1%A8%E5%8D%B3%E4%BB%A3%E7%A0%81%EF%BC%9A%E4%BD%BF%E7%94%A8-Diagrams-%E5%88%B6%E4%BD%9C%E4%BA%91%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E5%8E%9F%E5%9E%8B%E5%9B%BE/</id>
    <published>2020-02-16T05:50:07.000Z</published>
    <updated>2020-07-24T15:00:04.376Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>昨天发现了一款非常不错的云系统架构原型图制作库 <a href="https://github.com/mingrammer/diagrams" title="Diagrams" target="_blank" rel="noopener">Diagrams</a>，通过它，我们便可以使用代码的方式绘制诸如阿里云、AWS、Azure、K8S 等系统架构原型图。</p><p>相比于在 UI 上对各种图标进行拖拽和调整，这种方式更符合我们程序员的使用习惯。</p><p>本文不仅要介绍下这个库，也想说说我是如何参与到这个库中以支持阿里云资源。</p><h2 id="二、安装"><a href="#二、安装" class="headerlink" title="二、安装"></a>二、安装</h2><p><code>Diagrams</code> 使用 <a href="https://www.graphviz.org/" title="Graphviz" target="_blank" rel="noopener">Graphviz</a> 来渲染图表，在安装 <code>diagrams</code> 之前需要先<a href="https://graphviz.gitlab.io/download/" title="安装 Graphviz" target="_blank" rel="noopener">安装 Graphviz</a>。</p><blockquote><p>macOS 用户（如果使用 <a href="https://brew.sh/" target="_blank" rel="noopener">Homebrew</a>）可以使用 <code>brew install graphviz</code> 的方式来安装 <code>Graphviz</code>。</p></blockquote><p>安装 <code>diagrams</code> 的方式有多种，通过 <code>pip</code>、<code>pipenv</code> 和 <code>poetry</code> 均可：</p><figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 使用 pip (pip3)</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> pip install diagrams</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 使用 pipenv</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> pipenv install diagrams</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 使用 poetry</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> poetry add diagrams</span></span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="三、快速开始"><a href="#三、快速开始" class="headerlink" title="三、快速开始"></a>三、快速开始</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># diagram.py</span></span><br><span class="line"><span class="keyword">from</span> diagrams <span class="keyword">import</span> Diagram</span><br><span class="line"><span class="keyword">from</span> diagrams.alibabacloud.network <span class="keyword">import</span> SLB</span><br><span class="line"><span class="keyword">from</span> diagrams.alibabacloud.compute <span class="keyword">import</span> ECS</span><br><span class="line"><span class="keyword">from</span> diagrams.alibabacloud.database <span class="keyword">import</span> RDS</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> Diagram(<span class="string">"Web Service"</span>, show=<span class="keyword">False</span>):</span><br><span class="line">    SLB(<span class="string">"lb"</span>) &gt;&gt; ECS(<span class="string">"web"</span>) &gt;&gt; RDS(<span class="string">"userdb"</span>)</span><br></pre></td></tr></table></figure><p>执行后，就能生成如下架构图：</p><figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> python diagram.py</span></span><br></pre></td></tr></table></figure><img src="/2020/02/16/图表即代码：使用-Diagrams-制作云系统架构原型图/1.jpg"><h2 id="四、指南"><a href="#四、指南" class="headerlink" title="四、指南"></a>四、指南</h2><p><code>Diagrams</code> 库非常容易掌握，我们仅需要掌握三个概念就能轻松绘制云系统架构图：</p><ul><li><code>Diagram</code>：这是表示图的最主要的对象，代表一个架构图</li><li><code>Node</code>：表示一个节点或系统组件，比如<code>快速开始</code>中的<code>SLB</code>、<code>ECS</code>和<code>RDS</code>都是架构图中的节点</li><li><code>Cluster</code>：表示集群或分组，可将多个节点放到一个集群中</li></ul><h3 id="4-1-图-Diagram"><a href="#4-1-图-Diagram" class="headerlink" title="4.1 图 Diagram"></a>4.1 图 Diagram</h3><p>使用 <code>Diagram</code> 类来创建图环境上下文，使用 <code>with</code> 语法来使用这个上下文。<code>Diagram</code> 的第一个参数是会被用作架构图的名称以及输出的图片文件名（转换为小写+下划线）。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> diagrams <span class="keyword">import</span> Diagram</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.compute <span class="keyword">import</span> EC2</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> Diagram(<span class="string">"Simple Diagram"</span>):</span><br><span class="line">    EC2(<span class="string">"web"</span>)</span><br></pre></td></tr></table></figure><p>运行上述代码，会生成一个包含 <code>EC2</code> 节点的架构图，并存放在当前的 <code>simple_diagram.png</code> 中。</p><p><code>Diagram</code> 类还支持如下参数：</p><ul><li><code>outformat</code>：指定输出图片的类型，默认是 <code>png</code>，可以是 <code>png</code>、<code>jpg</code>、<code>svg</code> 和 <code>pdf</code></li><li><code>show</code>：指定是否显示图片，默认是 <code>False</code></li><li><code>graph_attr</code>、<code>node_attr</code> 和 <code>edge_attr</code>：指定 <code>Graphviz</code> 属性选项，用来控制图、点、线的样式，详情查看 <a href="https://www.graphviz.org/doc/info/attrs.html" title="参考链接" target="_blank" rel="noopener">参考链接</a></li></ul><h3 id="4-2-节点-Node"><a href="#4-2-节点-Node" class="headerlink" title="4.2 节点 Node"></a>4.2 节点 Node</h3><p>目前，<code>Diagrams</code> 支持五类云资源节点，分别是 <code>AWS</code>、<code>Azure</code>、<code>AlibabaCloud</code>、<code>GCP</code> 和 <code>K8S</code>。</p><p>节点之间的关系使用操作符来表示，分别是：</p><ul><li><code>&gt;&gt;</code>：左节点指向右节点</li><li><code>&lt;&lt;</code>：右节点指向左节点</li><li><code>-</code>：节点互相连接，没有方向</li></ul><p>以下是一个例子：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> diagrams <span class="keyword">import</span> Diagram</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.compute <span class="keyword">import</span> EC2</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.database <span class="keyword">import</span> RDS</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.network <span class="keyword">import</span> ELB</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.storage <span class="keyword">import</span> S3</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> Diagram(<span class="string">"Web Services"</span>, show=<span class="keyword">False</span>):</span><br><span class="line">    ELB(<span class="string">"lb"</span>) &gt;&gt; EC2(<span class="string">"web"</span>) &gt;&gt; RDS(<span class="string">"userdb"</span>) &gt;&gt; S3(<span class="string">"store"</span>)</span><br><span class="line">    ELB(<span class="string">"lb"</span>) &gt;&gt; EC2(<span class="string">"web"</span>) &gt;&gt; RDS(<span class="string">"userdb"</span>) &lt;&lt; EC2(<span class="string">"stat"</span>)</span><br><span class="line">    (ELB(<span class="string">"lb"</span>) &gt;&gt; EC2(<span class="string">"web"</span>)) - EC2(<span class="string">"web"</span>) &gt;&gt; RDS(<span class="string">"userdb"</span>)</span><br></pre></td></tr></table></figure><img src="/2020/02/16/图表即代码：使用-Diagrams-制作云系统架构原型图/2.jpg"><p><code>Diagrams</code> 不仅支持单个节点的关系建立，还支持一组节点和其他节点的关系建立，使用 <code>list</code> 来表示一组节点。示例如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> diagrams <span class="keyword">import</span> Diagram</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.compute <span class="keyword">import</span> EC2</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.database <span class="keyword">import</span> RDS</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.network <span class="keyword">import</span> ELB</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> Diagram(<span class="string">"Grouped Workers"</span>, show=<span class="keyword">False</span>, direction=<span class="string">"TB"</span>):</span><br><span class="line">    ELB(<span class="string">"lb"</span>) &gt;&gt; [EC2(<span class="string">"worker1"</span>),</span><br><span class="line">                  EC2(<span class="string">"worker2"</span>),</span><br><span class="line">                  EC2(<span class="string">"worker3"</span>),</span><br><span class="line">                  EC2(<span class="string">"worker4"</span>),</span><br><span class="line">                  EC2(<span class="string">"worker5"</span>)] &gt;&gt; RDS(<span class="string">"events"</span>)</span><br></pre></td></tr></table></figure><img src="/2020/02/16/图表即代码：使用-Diagrams-制作云系统架构原型图/3.jpg"><h3 id="4-3-集群-组-Cluster"><a href="#4-3-集群-组-Cluster" class="headerlink" title="4.3 集群/组 Cluster"></a>4.3 集群/组 Cluster</h3><p>当我们需要在架构图上表示几个节点属于一个集群时，就要用到 <code>Cluster</code>。和 <code>Diagram</code> 的使用方式类似，它也是一个上下文管理器，使用 <code>with</code> 语法。<br>示例如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> diagrams <span class="keyword">import</span> Cluster, Diagram</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.compute <span class="keyword">import</span> ECS</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.database <span class="keyword">import</span> RDS</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.network <span class="keyword">import</span> Route53</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> Diagram(<span class="string">"Simple Web Service with DB Cluster"</span>, show=<span class="keyword">False</span>):</span><br><span class="line">    dns = Route53(<span class="string">"dns"</span>)</span><br><span class="line">    web = ECS(<span class="string">"service"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">with</span> Cluster(<span class="string">"DB Cluster"</span>):</span><br><span class="line">        db_master = RDS(<span class="string">"master"</span>)</span><br><span class="line">        db_master - [RDS(<span class="string">"slave1"</span>),</span><br><span class="line">                     RDS(<span class="string">"slave2"</span>)]</span><br><span class="line"></span><br><span class="line">    dns &gt;&gt; web &gt;&gt; db_master</span><br></pre></td></tr></table></figure><img src="/2020/02/16/图表即代码：使用-Diagrams-制作云系统架构原型图/4.jpg"><p><code>Diagrams</code> 还支持嵌套集群，只需嵌套使用 <code>with Cluster()</code> 即可：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> diagrams <span class="keyword">import</span> Cluster, Diagram</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.compute <span class="keyword">import</span> ECS, EKS, Lambda</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.database <span class="keyword">import</span> Redshift</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.integration <span class="keyword">import</span> SQS</span><br><span class="line"><span class="keyword">from</span> diagrams.aws.storage <span class="keyword">import</span> S3</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> Diagram(<span class="string">"Event Processing"</span>, show=<span class="keyword">False</span>):</span><br><span class="line">    source = EKS(<span class="string">"k8s source"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">with</span> Cluster(<span class="string">"Event Flows"</span>):</span><br><span class="line">        <span class="keyword">with</span> Cluster(<span class="string">"Event Workers"</span>):</span><br><span class="line">            workers = [ECS(<span class="string">"worker1"</span>),</span><br><span class="line">                       ECS(<span class="string">"worker2"</span>),</span><br><span class="line">                       ECS(<span class="string">"worker3"</span>)]</span><br><span class="line"></span><br><span class="line">        queue = SQS(<span class="string">"event queue"</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">with</span> Cluster(<span class="string">"Processing"</span>):</span><br><span class="line">            handlers = [Lambda(<span class="string">"proc1"</span>),</span><br><span class="line">                        Lambda(<span class="string">"proc2"</span>),</span><br><span class="line">                        Lambda(<span class="string">"proc3"</span>)]</span><br><span class="line"></span><br><span class="line">    store = S3(<span class="string">"events store"</span>)</span><br><span class="line">    dw = Redshift(<span class="string">"analytics"</span>)</span><br><span class="line"></span><br><span class="line">    source &gt;&gt; workers &gt;&gt; queue &gt;&gt; handlers</span><br><span class="line">    handlers &gt;&gt; store</span><br><span class="line">    handlers &gt;&gt; dw</span><br></pre></td></tr></table></figure><img src="/2020/02/16/图表即代码：使用-Diagrams-制作云系统架构原型图/5.jpg"><h2 id="五、我是如何贡献代码"><a href="#五、我是如何贡献代码" class="headerlink" title="五、我是如何贡献代码"></a>五、我是如何贡献代码</h2><p>看到 <code>Diagrams</code> 库时，我感到很兴奋。我们画示意图无外乎两种，一种是通过<code>UI</code>来画，一种是通过<code>DSL</code>来制作。在流程图、时序图方面，<a href="https://plantuml.com/zh/" title="PlantUML" target="_blank" rel="noopener">PlantUML</a> 是我很喜欢的 <code>DSL</code>，然而在云系统架构图方面，过去确实没发现相关的库，直到看到了 <code>Diagrams</code>。</p><p>在我看到 <code>Diagrams</code> 时，它还只是支持 <code>AWS</code>、<code>Azure</code>、<code>GCP</code> 和 <code>K8S</code>，我心想怎么能没有<code>阿里云</code>呢？这么好的库我岂不是用不了了。既然如此，不如自己动手，丰衣足食吧。阅读 <code>Diagrams</code> 的代码，会发现写的还真不错，代码清晰简单，还提供了完善的脚手架。</p><p>对于它所支持的云供应商（比如 <code>AWS</code>），当我们想更新里面的资源时，只需要在 <code>resources/aws</code> 文件夹中更新资源图片，然后执行 <code>./autogen.sh</code> 即可。<code>./autogen.sh</code> 会对 <code>resources/</code> 做这么几件事：</p><ul><li>将特定云供应商的 <code>svg</code> 图片转换为 <code>png</code></li><li>将特定云供应商的图片调整为圆角图片</li><li>自动生成节点类代码</li><li>自动生成文档</li><li>使用 <code>black</code> 格式化自动生成的代码</li></ul><p>对于它所不支持的云供应商（比如 <code>AlibabaCloud</code>），则要先修改脚手架和配置文件以支持新的云供应商，然后遵循上面的方法即可。具体改动内容可见 <a href="https://github.com/mingrammer/diagrams/pull/19" title="Diagrams 支持阿里云 PR" target="_blank" rel="noopener">此 PR</a>。</p><p>参与一个开源项目其实就是这么简单，当你发现满足不了你的需求时，就阅读它的源码以了解实现原理，然后再自己动手实现需求，最后就是向作者提个 PR。</p><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;昨天发现了一款非常不错的云系统架构原型图制作库 &lt;a href=&quot;https://github.com/mingrammer/diagrams&quot; title=&quot;Diagrams&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Diagrams&lt;/a&gt;，通过它，我们便可以使用代码的方式绘制诸如阿里云、AWS、Azure、K8S 等系统架构原型图。&lt;/p&gt;
&lt;p&gt;相比于在 UI 上对各种图标进行拖拽和调整，这种方式更符合我们程序员的使用习惯。&lt;/p&gt;
&lt;p&gt;本文不仅要介绍下这个库，也想说说我是如何参与到这个库中以支持阿里云资源。&lt;/p&gt;
&lt;h2 id=&quot;二、安装&quot;&gt;&lt;a href=&quot;#二、安装&quot; class=&quot;headerlink&quot; title=&quot;二、安装&quot;&gt;&lt;/a&gt;二、安装&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Diagrams&lt;/code&gt; 使用 &lt;a href=&quot;https://www.graphviz.org/&quot; title=&quot;Graphviz&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Graphviz&lt;/a&gt; 来渲染图表，在安装 &lt;code&gt;diagrams&lt;/code&gt; 之前需要先&lt;a href=&quot;https://graphviz.gitlab.io/download/&quot; title=&quot;安装 Graphviz&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;安装 Graphviz&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;macOS 用户（如果使用 &lt;a href=&quot;https://brew.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Homebrew&lt;/a&gt;）可以使用 &lt;code&gt;brew install graphviz&lt;/code&gt; 的方式来安装 &lt;code&gt;Graphviz&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装 &lt;code&gt;diagrams&lt;/code&gt; 的方式有多种，通过 &lt;code&gt;pip&lt;/code&gt;、&lt;code&gt;pipenv&lt;/code&gt; 和 &lt;code&gt;poetry&lt;/code&gt; 均可：&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;figcaption&gt;&lt;span&gt;script&lt;/span&gt;&lt;/figcaption&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;bash&quot;&gt; 使用 pip (pip3)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;bash&quot;&gt; pip install diagrams&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;bash&quot;&gt; 使用 pipenv&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;bash&quot;&gt; pipenv install diagrams&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;bash&quot;&gt; 使用 poetry&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;bash&quot;&gt; poetry add diagrams&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="Diagrams" scheme="http://prodesire.cn/tags/Diagrams/"/>
    
  </entry>
  
  <entry>
    <title>Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</title>
    <link href="http://prodesire.cn/2020/02/09/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9Aargparse%E3%80%81docopt%E3%80%81click-%E5%92%8C-fire-%E6%80%BB%E7%BB%93%E7%AF%87/"/>
    <id>http://prodesire.cn/2020/02/09/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9Aargparse%E3%80%81docopt%E3%80%81click-%E5%92%8C-fire-%E6%80%BB%E7%BB%93%E7%AF%87/</id>
    <published>2020-02-09T05:40:21.000Z</published>
    <updated>2020-07-24T15:00:04.356Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>在近半年的 Python 命令行旅程中，我们依次学习了 <code>argparse</code>、<code>docopt</code>、<code>click</code> 和 <code>fire</code> 库的特点和用法，逐步了解到 Python 命令行库的设计哲学与演变。<br>本文作为本次旅程的终点，希望从一个更高的视角对这些库进行横向对比，总结它们的异同点和使用场景，以期在应对不同场景时能够分析利弊，选择合适的库为己所用。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">本系列文章默认使用 Python 3 作为解释器进行讲解。</span><br><span class="line">若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~</span><br></pre></td></tr></table></figure><h2 id="二、设计理念"><a href="#二、设计理念" class="headerlink" title="二、设计理念"></a>二、设计理念</h2><p>在讨论各个库的设计理念之前，我们先设计一个<code>计算器程序</code>，其实这个例子在 <code>argparse</code> 库的第一篇讲解中出现过，也就是：</p><ul><li>命令行程序接受一个位置参数，它能出现多次，且是数字</li><li>默认情况下，命令行程序会求出给定的一串数字的最大值</li><li>如果指定了选项参数 <code>--sum</code>，那么就会将求出给定的一串数字的和</li></ul><p>希望从各个库实现该例子的代码中能进一步体会它们的设计理念。</p><a id="more"></a><h3 id="2-1、argparse"><a href="#2-1、argparse" class="headerlink" title="2.1、argparse"></a>2.1、argparse</h3><p><code>argparse</code> 的设计理念就是提供给你最细粒度的控制，你需要详细地告诉它参数是选项参数还是位置参数、参数值的类型是什么、该参数的处理动作是怎样的。<br>总之，它就像是一个没有智能分析能力的初代机器人，你需要告诉它明确的信息，它才会根据给定的信息去帮助你做事情。</p><p>以下示例为 <code>argparse</code> 实现的 <code>计算器程序</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> argparse</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 设置解析器</span></span><br><span class="line">parser = argparse.ArgumentParser(description=<span class="string">'Calculator Program.'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 定义参数</span></span><br><span class="line"><span class="comment"># 添加位置参数 nums，在帮助信息中显示为 num</span></span><br><span class="line"><span class="comment"># 其类型为 int，且支持输入多个，且至少需要提供一个</span></span><br><span class="line">parser.add_argument(<span class="string">'nums'</span>,  metavar=<span class="string">'num'</span>, type=int, nargs=<span class="string">'+'</span>,</span><br><span class="line">                    help=<span class="string">'a num for the accumulator'</span>)</span><br><span class="line"><span class="comment"># 添加选项参数 --sum，该参数被 parser 解析后所对应的属性名为 accumulate</span></span><br><span class="line"><span class="comment"># 若不提供 --sum，默认值为 max 函数，否则为 sum 函数</span></span><br><span class="line">parser.add_argument(<span class="string">'--sum'</span>, dest=<span class="string">'accumulate'</span>, action=<span class="string">'store_const'</span>,</span><br><span class="line">                    const=sum, default=max,</span><br><span class="line">                    help=<span class="string">'sum the nums (default: find the max)'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 解析参数</span></span><br><span class="line">args = parser.parse_args([<span class="string">'--sum'</span>, <span class="string">'1'</span>, <span class="string">'2'</span>, <span class="string">'3'</span>])</span><br><span class="line">print(args) <span class="comment"># 结果：Namespace(accumulate=&lt;built-in function sum&gt;, nums=[1, 2, 3])</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 业务逻辑</span></span><br><span class="line">result = args.accumulate(args.nums)</span><br><span class="line">print(result)  <span class="comment"># 基于上文的 ['--sum', '1', '2', '3'] 参数，accumulate 为 sum 函数，其结果为 6</span></span><br></pre></td></tr></table></figure><p>从上述示例可以看到，我们需要通过 <code>add_argument</code> 很明确地告诉 <code>argparse</code> 参数长什么样：</p><ul><li>它是位置参数 <code>nums</code>，还是选项参数 <code>--sum</code></li><li>它的类型是什么，比如 <code>type=int</code> 表示类型是 int</li><li>这个参数能重复出现几次，比如 <code>nargs=&#39;+&#39;</code> 表示至少提供 1 个</li><li>参数的是存什么的，比如 <code>action=&#39;store_const&#39;</code> 表示存常量</li></ul><p>然后它才根据给定的这些元信息来解析命令行参数（也就是示例中的 <code>[&#39;--sum&#39;, &#39;1&#39;, &#39;2&#39;, &#39;3&#39;]</code>）。</p><p>这是很计算机的思维，虽然冗长，但也带来了灵活性。</p><h3 id="2-2、docopt"><a href="#2-2、docopt" class="headerlink" title="2.2、docopt"></a>2.2、docopt</h3><p>从 <code>argparse</code> 的理念可以看出，它是命令式的。这时候 <code>docopt</code> 另辟蹊径，声明式是不是也可以？一个命令行程序的帮助信息其实已然包含了这个命令行的完整元信息，那不就可以通过定义帮助信息来定义命令行？<code>docopt</code> 就是基于这样的想法去设计的。</p><p>声明式的好处在于只要你掌握了声明式的语法，那么定义命令行的元信息就会很简单。</p><p>以下示例为 <code>docopt</code> 实现的 <code>计算器程序</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 定义接口描述/帮助信息</span></span><br><span class="line"><span class="string">"""Calculator Program.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Usage:</span></span><br><span class="line"><span class="string">  calculator.py [--sum] &lt;num&gt;...</span></span><br><span class="line"><span class="string">  calculator.py (-h | --help)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Options:</span></span><br><span class="line"><span class="string">  -h --help     Show help.</span></span><br><span class="line"><span class="string">  --sum         Sum the nums (default: find the max).</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> docopt <span class="keyword">import</span> docopt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 解析命令行</span></span><br><span class="line">arguments = docopt(__doc__, options_first=<span class="keyword">True</span>, argv=[<span class="string">'--sum'</span>, <span class="string">'1'</span>, <span class="string">'2'</span>, <span class="string">'3'</span>])</span><br><span class="line">print(arguments) <span class="comment"># 结果：&#123;'--help': False, '--sum': True, '&lt;num&gt;': ['1', '2', '3']&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 业务逻辑</span></span><br><span class="line">nums = (int(num) <span class="keyword">for</span> num <span class="keyword">in</span> arguments[<span class="string">'&lt;num&gt;'</span>])</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> arguments[<span class="string">'--sum'</span>]:</span><br><span class="line">    result = sum(nums)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    result = max(nums)</span><br><span class="line"></span><br><span class="line">print(result) <span class="comment"># 基于上文的 ['--sum', '1', '2', '3'] 参数，处理函数为 sum 函数，其结果为 6</span></span><br></pre></td></tr></table></figure><p>从上述示例可以看到，我们通过 <code>__doc__</code> 定义了接口描述，这和 <code>argparse</code> 中 <code>add_argument</code> 是等价的，然后 <code>docopt</code> 便会根据这个元信息把命令行参数转换为一个字典。业务逻辑中就需要对这个字典进行处理。</p><p>对比与 <code>argparse</code>：</p><ul><li>对于更为复杂的命令程序，元信息的定义上 <code>docopt</code> 会更加简单</li><li>然而在业务逻辑的处理上，由于 <code>argparse</code> 在一些简单参数的处理上会更加便捷（比如示例中的情形），相对来说 <code>docopt</code> 转换为字典后就把所有处理交给业务逻辑的方式会更加复杂</li></ul><h3 id="2-3、click"><a href="#2-3、click" class="headerlink" title="2.3、click"></a>2.3、click</h3><p>命令行程序本质上是定义参数和处理参数，而处理参数的逻辑一定是与所定义的参数有关联的。那可不可以用函数和装饰器来实现处理参数逻辑与定义参数的关联呢？而 <code>click</code> 正好就是以这种使用方式来设计的。</p><p><code>click</code> 使用装饰器的好处就在于用装饰器优雅的语法将参数定义和处理逻辑整合在一起，从而暗示了路由关系。相比于 <code>argparse</code> 和 <code>docopt</code> 需要自行对解析后的参数来做路由关系，简单了不少。</p><p>以下示例为 <code>click</code> 实现的 <code>计算器程序</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> click</span><br><span class="line"></span><br><span class="line">sys.argv = [<span class="string">'calculator.py'</span>, <span class="string">'--sum'</span>, <span class="string">'1'</span>, <span class="string">'2'</span>, <span class="string">'3'</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 定义参数</span></span><br><span class="line"><span class="meta">@click.command()</span></span><br><span class="line"><span class="meta">@click.argument('nums', nargs=-1, type=int)</span></span><br><span class="line"><span class="meta">@click.option('--sum', 'use_sum', is_flag=True, help='sum the nums (default: find the max)')</span></span><br><span class="line"><span class="comment"># 1. 业务逻辑</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">calculator</span><span class="params">(nums, use_sum)</span>:</span></span><br><span class="line">    <span class="string">"""Calculator Program."""</span></span><br><span class="line">    print(nums, use_sum) <span class="comment"># 输出：(1, 2, 3) True</span></span><br><span class="line">    <span class="keyword">if</span> use_sum:</span><br><span class="line">        result = sum(nums)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        result = max(nums)</span><br><span class="line"></span><br><span class="line">    print(result) <span class="comment"># 基于上文的 ['--sum', '1', '2', '3'] 参数，处理函数为 sum 函数，其结果为 6</span></span><br><span class="line"></span><br><span class="line">calculator()</span><br></pre></td></tr></table></figure><p>从上述示例可以看出，参数和对应的处理逻辑非常好地绑定在了一起，看上去就很直观，使得我们可以明确了解参数会怎么处理，这在有大量参数时显得尤为重要，这边是 <code>click</code> 相比于 <code>argparse</code> 和 <code>docopt</code> 最明显的优势。</p><p>此外，<code>click</code> 还内置了很多实用工具和额外能力，比如说 Bash 补全、颜色、分页支持、进度条等诸多实用功能，可谓是如虎添翼。</p><h3 id="2-4、fire"><a href="#2-4、fire" class="headerlink" title="2.4、fire"></a>2.4、fire</h3><p><code>fire</code> 则是用一种面向广义对象的方式来玩转命令行，这种对象可以是类、函数、字典、列表等，它更加灵活，也更加简单。你都不需要定义参数类型，<code>fire</code> 会根据输入和参数默认值来自动判断，这无疑进一步简化了实现过程。</p><p>以下示例为 <code>click</code> 实现的 <code>计算器程序</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line">sys.argv = [<span class="string">'calculator.py'</span>, <span class="string">'1'</span>, <span class="string">'2'</span>, <span class="string">'3'</span>, <span class="string">'--sum'</span>]</span><br><span class="line"></span><br><span class="line">builtin_sum = sum</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 业务逻辑</span></span><br><span class="line"><span class="comment"># sum=False，暗示它是一个选项参数 --sum，不提供的时候为 False</span></span><br><span class="line"><span class="comment"># *nums 暗示它是一个能提供任意数量的位置参数</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">calculator</span><span class="params">(sum=False, *nums)</span>:</span></span><br><span class="line">    <span class="string">"""Calculator Program."""</span></span><br><span class="line">    print(sum, nums) <span class="comment"># 输出：True (1, 2, 3)</span></span><br><span class="line">    <span class="keyword">if</span> sum:</span><br><span class="line">        result = builtin_sum(nums)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        result = max(nums)</span><br><span class="line"></span><br><span class="line">    print(result) <span class="comment"># 基于上文的 ['1', '2', '3', '--sum'] 参数，处理函数为 sum 函数，其结果为 6</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">fire.Fire(calculator)</span><br></pre></td></tr></table></figure><p>从上述示例可以看出，<code>fire</code> 提供的方式无疑是最简单、并且最 Pythonic 的了。我们只需关注业务逻辑，而命令行参数的定义则和函数参数的定义融为了一体。</p><p>不过，有利自然也有弊，比如 <code>nums</code> 并没有说是什么类型，也就意味着输入字符串’abc’也是合法的，这就意味着一个严格的命令行程序必须在自己的业务逻辑中来对期望的类型进行约束。</p><h2 id="三、横向对比"><a href="#三、横向对比" class="headerlink" title="三、横向对比"></a>三、横向对比</h2><p>最后，我们横向对比下<code>argparse</code>、<code>docopt</code>、<code>click</code> 和 <code>fire</code> 库的各项功能和特点：</p><table><thead><tr><th></th><th style="text-align:left">argpase</th><th style="text-align:left">docopt</th><th style="text-align:left">click</th><th style="text-align:left">fire</th></tr></thead><tbody><tr><td>使用步骤数</td><td style="text-align:left">4 步</td><td style="text-align:left">3 步</td><td style="text-align:left">2 步</td><td style="text-align:left">1 步</td></tr><tr><td>使用步骤数</td><td style="text-align:left">1. 设置解析器<br>2. 定义参数<br>3. 解析命令行<br>4. 业务逻辑</td><td style="text-align:left">1. 定义接口描述<br>2. 解析命令行<br>3. 业务逻辑</td><td style="text-align:left">1. 业务逻辑<br>2. 定义参数</td><td style="text-align:left">1. 业务逻辑</td></tr><tr><td>选项参数<br>（如 <code>--sum</code>）</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>位置参数<br>（如 <code>X Y</code>）</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>参数默认值<br></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>互斥选项<br>（如 <code>--car</code> 和 <code>--bus</code> 只能二选一）</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="yellow">✔</font><br>可通过第三方库支持</td><td style="text-align:left"><font color="red">✘</font></td></tr><tr><td>可变参数<br>（如指定多个 <code>--file</code>）</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>嵌套/父子命令<br></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>工具箱<br></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>链式命令调用<br></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td></tr><tr><td>类型约束</td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="red">✘</font></td><td style="text-align:left"><font color="green">✔</font></td><td style="text-align:left"><font color="red">✘</font></td></tr></tbody></table><p>Python 的命令行库种类繁多、各具特色。结合上面的总结，可以选择出符合使用场景的库，如果几个库都符合，那么就根据你更偏爱的风格来选择。这些库都很优秀，其背后的思想很是值得我们学习和扩展。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ul><li><a href="/2019/08/13/Python-命令行之旅：初探-argparse/" title="Python 命令行之旅：初探 argparse">Python 命令行之旅：初探 argparse</a></li><li><a href="/2019/08/20/Python-命令行之旅：深入-argparse（一）/" title="Python 命令行之旅：深入 argparse（一）">Python 命令行之旅：深入 argparse（一）</a></li><li><a href="/2019/08/27/Python-命令行之旅：深入-argparse（二）/" title="Python 命令行之旅：深入 argparse（二）">Python 命令行之旅：深入 argparse（二）</a></li><li><a href="/2019/09/04/Python-命令行之旅：使用-argparse-实现-git-命令/" title="Python 命令行之旅：使用 argparse 实现 git 命令">Python 命令行之旅：使用 argparse 实现 git 命令</a></li><li><a href="/2019/10/09/Python-命令行之旅：初探-docopt/" title="Python 命令行之旅：初探 docopt">Python 命令行之旅：初探 docopt</a></li><li><a href="/2019/10/15/Python-命令行之旅：深入-docopt/" title="Python 命令行之旅：深入 docopt">Python 命令行之旅：深入 docopt</a></li><li><a href="/2019/10/30/Python-命令行之旅：使用-docopt-实现-git-命令/" title="Python 命令行之旅：使用 docopt 实现 git 命令">Python 命令行之旅：使用 docopt 实现 git 命令</a></li><li><a href="/2019/11/04/Python-命令行之旅：初探-click/" title="Python 命令行之旅：初探 click">Python 命令行之旅：初探 click</a></li><li><a href="/2019/11/11/Python-命令行之旅：深入-click（一）/" title="Python 命令行之旅：深入 click（一）">Python 命令行之旅：深入 click（一）</a></li><li><a href="/2019/11/17/Python-命令行之旅：深入-click（二）/" title="Python 命令行之旅：深入 click（二）">Python 命令行之旅：深入 click（二）</a></li><li><a href="/2019/11/25/Python-命令行之旅：深入-click（三）/" title="Python 命令行之旅：深入 click（三）">Python 命令行之旅：深入 click（三）</a></li><li><a href="/2019/12/09/Python-命令行之旅：深入-click（四）/" title="Python 命令行之旅：深入 click（四）">Python 命令行之旅：深入 click（四）</a></li><li><a href="/2019/12/14/Python-命令行之旅：使用-click-实现-git-命令/" title="Python 命令行之旅：使用 click 实现 git 命令">Python 命令行之旅：使用 click 实现 git 命令</a></li><li><a href="/2019/12/22/Python-命令行之旅：初探-fire/" title="Python 命令行之旅：初探 fire">Python 命令行之旅：初探 fire</a></li><li><a href="/2019/12/29/Python-命令行之旅：深入-fire（一）/" title="Python 命令行之旅：深入 fire（一）">Python 命令行之旅：深入 fire（一）</a></li><li><a href="/2020/01/05/Python-命令行之旅：深入-fire（二）/" title="Python 命令行之旅：深入 fire（二）">Python 命令行之旅：深入 fire（二）</a></li><li><a href="/2020/01/12/Python-命令行之旅：使用-fire-实现-git-命令/" title="Python 命令行之旅：使用 fire 实现 git 命令">Python 命令行之旅：使用 fire 实现 git 命令</a></li><li><a href="/2020/02/09/Python-命令行之旅：argparse、docopt、click-和-fire-总结篇/" title="Python 命令行之旅：argparse、docopt、click 和 fire 总结篇">Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</a></li><li><a href="/2020/07/02/Python-命令行大乱斗/" title="Python 命令行大乱斗">Python 命令行大乱斗</a></li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;在近半年的 Python 命令行旅程中，我们依次学习了 &lt;code&gt;argparse&lt;/code&gt;、&lt;code&gt;docopt&lt;/code&gt;、&lt;code&gt;click&lt;/code&gt; 和 &lt;code&gt;fire&lt;/code&gt; 库的特点和用法，逐步了解到 Python 命令行库的设计哲学与演变。&lt;br&gt;本文作为本次旅程的终点，希望从一个更高的视角对这些库进行横向对比，总结它们的异同点和使用场景，以期在应对不同场景时能够分析利弊，选择合适的库为己所用。&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;本系列文章默认使用 Python 3 作为解释器进行讲解。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&quot;二、设计理念&quot;&gt;&lt;a href=&quot;#二、设计理念&quot; class=&quot;headerlink&quot; title=&quot;二、设计理念&quot;&gt;&lt;/a&gt;二、设计理念&lt;/h2&gt;&lt;p&gt;在讨论各个库的设计理念之前，我们先设计一个&lt;code&gt;计算器程序&lt;/code&gt;，其实这个例子在 &lt;code&gt;argparse&lt;/code&gt; 库的第一篇讲解中出现过，也就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令行程序接受一个位置参数，它能出现多次，且是数字&lt;/li&gt;
&lt;li&gt;默认情况下，命令行程序会求出给定的一串数字的最大值&lt;/li&gt;
&lt;li&gt;如果指定了选项参数 &lt;code&gt;--sum&lt;/code&gt;，那么就会将求出给定的一串数字的和&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;希望从各个库实现该例子的代码中能进一步体会它们的设计理念。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/categories/Python/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
      <category term="argparse" scheme="http://prodesire.cn/tags/argparse/"/>
    
      <category term="click" scheme="http://prodesire.cn/tags/click/"/>
    
      <category term="docopt" scheme="http://prodesire.cn/tags/docopt/"/>
    
      <category term="fire" scheme="http://prodesire.cn/tags/fire/"/>
    
  </entry>
  
  <entry>
    <title>一行命令自动戴上口罩</title>
    <link href="http://prodesire.cn/2020/01/31/%E4%B8%80%E8%A1%8C%E5%91%BD%E4%BB%A4%E8%87%AA%E5%8A%A8%E6%88%B4%E4%B8%8A%E5%8F%A3%E7%BD%A9/"/>
    <id>http://prodesire.cn/2020/01/31/%E4%B8%80%E8%A1%8C%E5%91%BD%E4%BB%A4%E8%87%AA%E5%8A%A8%E6%88%B4%E4%B8%8A%E5%8F%A3%E7%BD%A9/</id>
    <published>2020-01-31T05:37:59.000Z</published>
    <updated>2020-07-24T15:00:04.364Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>2019 年底开始蔓延的新型肺炎疫情牵动人心，作为个体，我们力所能及的就是尽量待在家中少出门。</p><p>看到一些朋友叫设计同学帮忙给自己的头像戴上口罩，作为技术人，心想一定还有更多人有这样的诉求，不如开发一个简单的程序来实现这个需求，也算是帮助设计姐姐减少工作量。</p><p>于是花了些时间，写了一个叫做 <a href="https://github.com/Prodesire/face-mask" title="face-mask" target="_blank" rel="noopener">face-mask</a> 的命令行工具，能够轻松的给图片中的人像戴上口罩，而且口罩的方向和大小都是适应人脸的哦~</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="安装-face-mask"><a href="#安装-face-mask" class="headerlink" title="安装 face-mask"></a>安装 <code>face-mask</code></h3><blockquote><p>确保 Python 版本在 3.6 及以上</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install face-mask</span><br></pre></td></tr></table></figure><h3 id="使用-face-mask"><a href="#使用-face-mask" class="headerlink" title="使用 face-mask"></a>使用 <code>face-mask</code></h3><p>直接指定图片路径即可为图片中的人像戴上口罩，并会生成一个新的图片（额外有 <code>-with-mask</code> 后缀）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">face-mask /path/to/face/picture</span><br></pre></td></tr></table></figure><p>通过指定 <code>--show</code> 选项，还可以使用默认图片查看器打开新生成的图片：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">face-mask /path/to/face/picture --show</span><br></pre></td></tr></table></figure><a id="more"></a><h3 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h3><p>给一个人戴上口罩<br><img src="/2020/01/31/一行命令自动戴上口罩/1.jpg"></p><p>给多个人戴上口罩<br><img src="/2020/01/31/一行命令自动戴上口罩/2.jpg"></p><p>给动漫人物戴上口罩<br><img src="/2020/01/31/一行命令自动戴上口罩/3.jpg"></p><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><h3 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h3><p>要想实现上面的效果，我们应该怎么做？不妨这么想：</p><ul><li>首先是识别出人的鼻子(nose_bridge)和脸轮廓(chin)</li><li>通过脸轮廓确定出脸左点（chin_left_point）、脸底点（chin_bottom_point）和脸右点（chin_right_point）</li><li>由鼻子和脸底点确定口罩大小的高度、中心线</li><li>将口罩左右平均分为两个部分<ul><li>调整左口罩大小，宽度为脸左点到中心线的距离</li><li>调整右口罩大小，宽度为脸右点到中心线的距离</li><li>合并左右口罩为新口罩</li></ul></li><li>旋转新口罩，角度为中心线相对于 y 轴的旋转角</li><li>将新口罩放在原图适当位置</li></ul><p>关于<strong>人脸识别</strong>，可以使用 <a href="https://github.com/ageitgey/face_recognition" title="face_recognition" target="_blank" rel="noopener">face_recognition</a> 库进行识别。</p><p>关于<strong>图像处理</strong>，可以使用 <a href="https://pillow.readthedocs.io/" title="Pillow" target="_blank" rel="noopener">Pillow</a> 库进行处理。</p><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><p>有了思路之后，实现就是件相对轻松的事情。不过对库的熟悉和图片的变换计算可能要花些时间。</p><p>详细的代码请阅读 <a href="https://github.com/Prodesire/face-mask" title="face-mask" target="_blank" rel="noopener">face-mask</a>。这里仅说明下最核心的步骤。</p><h4 id="人脸识别"><a href="#人脸识别" class="headerlink" title="人脸识别"></a>人脸识别</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> face_recognition</span><br><span class="line"></span><br><span class="line">face_image_np = face_recognition.load_image_file(<span class="string">'/path/to/face/picture'</span>)</span><br><span class="line">face_landmarks = face_recognition.face_landmarks(face_image_np)</span><br></pre></td></tr></table></figure><p>借助 <code>face_recognition</code> 库可以轻松的识别出人像，最终得到的 <code>face_landmarks</code> 是一个列表，里面的每个 <code>face_landmark</code> 都表示一个人像数据。</p><p><code>face_landmark</code> 是一个字典，其中的键表示人像特征，值表示该特征的点的列表。比如：</p><ul><li>键 <code>nose_bridge</code> 表示鼻梁</li><li>键 <code>chin</code> 表示脸颊</li></ul><p>我们需要根据每个 <code>face_landmark</code>，给对应的头像戴上口罩。</p><h4 id="获得鼻子和脸颊的特征点"><a href="#获得鼻子和脸颊的特征点" class="headerlink" title="获得鼻子和脸颊的特征点"></a>获得鼻子和脸颊的特征点</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><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">nose_bridge = face_landmark[<span class="string">'nose_bridge'</span>]</span><br><span class="line">nose_point = nose_bridge[len(nose_bridge) * <span class="number">1</span> // <span class="number">4</span>]</span><br><span class="line">nose_v = np.array(nose_point)</span><br><span class="line"></span><br><span class="line">chin = face_landmark[<span class="string">'chin'</span>]</span><br><span class="line">chin_len = len(chin)</span><br><span class="line">chin_bottom_point = chin[chin_len // <span class="number">2</span>]</span><br><span class="line">chin_bottom_v = np.array(chin_bottom_point)</span><br><span class="line">chin_left_point = chin[chin_len // <span class="number">8</span>]</span><br><span class="line">chin_right_point = chin[chin_len * <span class="number">7</span> // <span class="number">8</span>]</span><br></pre></td></tr></table></figure><p>通过上述代码，我们获得了：</p><ul><li>表示上鼻梁的一个点 <code>nose_point</code></li><li>表示脸左点 <code>chin_left_point</code></li><li>表示脸右点 <code>chin_right_point</code></li><li>表示脸底点 <code>chin_bottom_point</code></li></ul><h4 id="拆分、缩放和合并口罩"><a href="#拆分、缩放和合并口罩" class="headerlink" title="拆分、缩放和合并口罩"></a>拆分、缩放和合并口罩</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"></span><br><span class="line">_face_img = Image.fromarray(face_image_np)</span><br><span class="line">_mask_img = Image.open(<span class="string">'/path/to/mask/picture'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># split mask and resize</span></span><br><span class="line">width = _mask_img.width</span><br><span class="line">height = _mask_img.height</span><br><span class="line">width_ratio = <span class="number">1.2</span></span><br><span class="line">new_height = int(np.linalg.norm(nose_v - chin_bottom_v))</span><br><span class="line"></span><br><span class="line"><span class="comment"># left</span></span><br><span class="line">mask_left_img = _mask_img.crop((<span class="number">0</span>, <span class="number">0</span>, width // <span class="number">2</span>, height))</span><br><span class="line">mask_left_width = get_distance_from_point_to_line(chin_left_point, nose_point, chin_bottom_point)</span><br><span class="line">mask_left_width = int(mask_left_width * width_ratio)</span><br><span class="line">mask_left_img = mask_left_img.resize((mask_left_width, new_height))</span><br><span class="line"></span><br><span class="line"><span class="comment"># right</span></span><br><span class="line">mask_right_img = _mask_img.crop((width // <span class="number">2</span>, <span class="number">0</span>, width, height))</span><br><span class="line">mask_right_width = get_distance_from_point_to_line(chin_right_point, nose_point, chin_bottom_point)</span><br><span class="line">mask_right_width = int(mask_right_width * width_ratio)</span><br><span class="line">mask_right_img = mask_right_img.resize((mask_right_width, new_height))</span><br><span class="line"></span><br><span class="line"><span class="comment"># merge mask</span></span><br><span class="line">size = (mask_left_img.width + mask_right_img.width, new_height)</span><br><span class="line">mask_img = Image.new(<span class="string">'RGBA'</span>, size)</span><br><span class="line">mask_img.paste(mask_left_img, (<span class="number">0</span>, <span class="number">0</span>), mask_left_img)</span><br><span class="line">mask_img.paste(mask_right_img, (mask_left_img.width, <span class="number">0</span>), mask_right_img)</span><br></pre></td></tr></table></figure><p>上述代码主要做了如下内容：</p><ul><li>将口罩左右平均分为两个部分</li><li>调整左口罩大小，宽度为脸左点到中心线的距离 * 宽度系数 1.2</li><li>调整右口罩大小，宽度为脸右点到中心线的距离 * 宽度系数 1.2</li><li>合并左右口罩为新口罩</li></ul><p><code>get_distance_from_point_to_line</code> 用来获取一个点到一条线的距离，具体实现可看源代码。</p><p><code>width_ratio</code> 是宽度系数，用来适当扩大口罩。原因我们是根据脸颊的宽度计算口罩的宽度，但口罩是待在耳朵上的，真实宽度应该要更宽。</p><h4 id="旋转口罩、并放到原图适当位置"><a href="#旋转口罩、并放到原图适当位置" class="headerlink" title="旋转口罩、并放到原图适当位置"></a>旋转口罩、并放到原图适当位置</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># rotate mask</span></span><br><span class="line">angle = np.arctan2(chin_bottom_point[<span class="number">1</span>] - nose_point[<span class="number">1</span>], chin_bottom_point[<span class="number">0</span>] - nose_point[<span class="number">0</span>])</span><br><span class="line">rotated_mask_img = mask_img.rotate(angle, expand=<span class="keyword">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># calculate mask location</span></span><br><span class="line">center_x = (nose_point[<span class="number">0</span>] + chin_bottom_point[<span class="number">0</span>]) // <span class="number">2</span></span><br><span class="line">center_y = (nose_point[<span class="number">1</span>] + chin_bottom_point[<span class="number">1</span>]) // <span class="number">2</span></span><br><span class="line"></span><br><span class="line">offset = mask_img.width // <span class="number">2</span> - mask_left_img.width</span><br><span class="line">radian = angle * np.pi / <span class="number">180</span></span><br><span class="line">box_x = center_x + int(offset * np.cos(radian)) - rotated_mask_img.width // <span class="number">2</span></span><br><span class="line">box_y = center_y + int(offset * np.sin(radian)) - rotated_mask_img.height // <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># add mask</span></span><br><span class="line">_face_img.paste(mask_img, (box_x, box_y), mask_img)</span><br></pre></td></tr></table></figure><p>上述代码主要做了如下内容：</p><ul><li>旋转新口罩，角度为中心线相对于 y 轴的旋转角</li><li>计算口罩应该放置的坐标</li><li>将新口罩放在原图的计算出的坐标下</li></ul><p>最后就是将新图片保存到本地路径，代码不再展示。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>我们借助 <code>face_recognition</code> 库可以轻松的识别出人像，然后根据脸颊的宽度和鼻梁位置计算出口罩的大小、方向和位置，并最终生成出戴上口罩的图片。整个过程并不复杂，但在坐标计算上要格外小心，如此，我们便打造了一个短小精悍的“自动戴上口罩”程序！</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;2019 年底开始蔓延的新型肺炎疫情牵动人心，作为个体，我们力所能及的就是尽量待在家中少出门。&lt;/p&gt;
&lt;p&gt;看到一些朋友叫设计同学帮忙给自己的头像戴上口罩，作为技术人，心想一定还有更多人有这样的诉求，不如开发一个简单的程序来实现这个需求，也算是帮助设计姐姐减少工作量。&lt;/p&gt;
&lt;p&gt;于是花了些时间，写了一个叫做 &lt;a href=&quot;https://github.com/Prodesire/face-mask&quot; title=&quot;face-mask&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;face-mask&lt;/a&gt; 的命令行工具，能够轻松的给图片中的人像戴上口罩，而且口罩的方向和大小都是适应人脸的哦~&lt;/p&gt;
&lt;h2 id=&quot;使用&quot;&gt;&lt;a href=&quot;#使用&quot; class=&quot;headerlink&quot; title=&quot;使用&quot;&gt;&lt;/a&gt;使用&lt;/h2&gt;&lt;h3 id=&quot;安装-face-mask&quot;&gt;&lt;a href=&quot;#安装-face-mask&quot; class=&quot;headerlink&quot; title=&quot;安装 face-mask&quot;&gt;&lt;/a&gt;安装 &lt;code&gt;face-mask&lt;/code&gt;&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;确保 Python 版本在 3.6 及以上&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;pip install face-mask&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id=&quot;使用-face-mask&quot;&gt;&lt;a href=&quot;#使用-face-mask&quot; class=&quot;headerlink&quot; title=&quot;使用 face-mask&quot;&gt;&lt;/a&gt;使用 &lt;code&gt;face-mask&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;直接指定图片路径即可为图片中的人像戴上口罩，并会生成一个新的图片（额外有 &lt;code&gt;-with-mask&lt;/code&gt; 后缀）：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;face-mask /path/to/face/picture&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;通过指定 &lt;code&gt;--show&lt;/code&gt; 选项，还可以使用默认图片查看器打开新生成的图片：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;face-mask /path/to/face/picture --show&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
    
  </entry>
  
  <entry>
    <title>一文掌握 Python 中的 &quot;is&quot; 和 &quot;==&quot;</title>
    <link href="http://prodesire.cn/2020/01/29/%E4%B8%80%E6%96%87%E6%8E%8C%E6%8F%A1-Python-%E4%B8%AD%E7%9A%84-is-%E5%92%8C-==/"/>
    <id>http://prodesire.cn/2020/01/29/%E4%B8%80%E6%96%87%E6%8E%8C%E6%8F%A1-Python-%E4%B8%AD%E7%9A%84-is-%E5%92%8C-==/</id>
    <published>2020-01-29T08:42:42.000Z</published>
    <updated>2020-07-24T15:00:04.364Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>Python 的 “is” 和 “==” 想必大家都不陌生，我们在比较变量和字面量时常常用到它们，可是它们的区别在哪里？什么情况下该用 <code>is</code>？什么情况下该用 <code>==</code>？这成了不少人心中的困惑。</p><p>当我们判断一个变量是否为 <code>None</code> 时，通常会用 <code>is</code>:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="keyword">None</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a <span class="keyword">is</span> <span class="keyword">None</span></span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b = <span class="number">1</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b <span class="keyword">is</span> <span class="keyword">None</span></span><br><span class="line"><span class="keyword">False</span></span><br></pre></td></tr></table></figure><p>而当我们判断一个变量是否为字面量（比如某个数值）时，通常会用 <code>==</code>:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="number">0</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a == <span class="number">0</span></span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a == <span class="number">1</span></span><br><span class="line"><span class="keyword">False</span></span><br></pre></td></tr></table></figure><p>要想解决上面的疑惑，我们首先需要搞明白 <code>is</code> 和 <code>==</code> 是什么。</p><a id="more"></a><h2 id="“is”-和-“-”-是什么"><a href="#“is”-和-“-”-是什么" class="headerlink" title="“is” 和 “==” 是什么"></a>“is” 和 “==” 是什么</h2><p><code>is</code> 用来检查身份（identity）的同一性，即两个变量是否指向同一个对象。</p><p><code>==</code> 用来检查值的相等性（equality），即两个变量的值是否相等。</p><p>身份的同一性同时也意味值的相等性，既然两个都指向同一个对象，那值就肯定相等。但是反之则不是。</p><h3 id="eq-魔法方法"><a href="#eq-魔法方法" class="headerlink" title="__eq__ 魔法方法"></a>__eq__ 魔法方法</h3><p>既然 <code>==</code> 是用来检查值的相等性，那么两个对象的值比较究竟是怎么进行的？</p><p>对于基本类型的对象的值比较，我们很容易理解。比如列表对象 [1, 2, 3] 的值比较就是比较列表的长度和列表中每个元素的值。</p><p>但是对于自定义的对象，该如何进行值比较？这里就涉及到了 <code>__eq__(self, other)</code> 魔法方法，我们可以通过该方法来实现对象的 <code>==</code> 逻辑。比如：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="class"><span class="keyword">class</span> <span class="title">Foo</span><span class="params">(object)</span>:</span></span><br><span class="line"><span class="meta">... </span>  <span class="function"><span class="keyword">def</span> <span class="title">__eq__</span><span class="params">(self, other)</span>:</span></span><br><span class="line"><span class="meta">... </span>    <span class="keyword">return</span> <span class="keyword">True</span></span><br><span class="line">...</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo = Foo()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo == <span class="number">1</span></span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo == <span class="keyword">None</span></span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo <span class="keyword">is</span> <span class="keyword">None</span></span><br><span class="line"><span class="keyword">False</span></span><br></pre></td></tr></table></figure><p>在上面的示例中，我们定义了 <code>Foo</code> 类，并实现了 <code>__eq__(self, other)</code> 方法，它永远返回 <code>True</code>，也就意味着和任何对象做值比较（<code>==</code>）结果都是 <code>True</code>。而当它做同一性比较时，比如和 <code>None</code> 比较，由于不是同一个对象，所以返回 <code>False</code>。</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="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b = a  <span class="comment"># b 指向 a，a 指向 [1, 2, 3]，所以 b 指向同一个 [1, 2, 3]</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b <span class="keyword">is</span> a</span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b == a</span><br><span class="line"><span class="keyword">True</span></span><br></pre></td></tr></table></figure><p>在上述示例中 <code>a</code> 和 <code>b</code> 均指向同一个列表对象 <code>[1, 2, 3]</code>，所以对它们使用 <code>is</code> 和 <code>==</code>，结果都是 <code>True</code>。</p><h3 id="示例二：指向不同对象（但值相同）的变量比较"><a href="#示例二：指向不同对象（但值相同）的变量比较" class="headerlink" title="示例二：指向不同对象（但值相同）的变量比较"></a>示例二：指向不同对象（但值相同）的变量比较</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b = a[:]  <span class="comment"># b 复制了一份 a 所指向的列表，产生新的 [1, 2, 3]</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b <span class="keyword">is</span> a</span><br><span class="line"><span class="keyword">False</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b == a</span><br><span class="line"><span class="keyword">True</span></span><br></pre></td></tr></table></figure><p>在上述示例中，由于 <code>b</code> 指向的是 <code>a</code> 的副本，也就是说 <code>a</code> 和 <code>b</code> 指向两个不同的对象，所以对它们使用 <code>is</code> 的结果是 <code>False</code>。但由于值相等，使用 <code>==</code> 的结果就是 <code>True</code>。</p><h3 id="示例三：指向字面量的变量比较"><a href="#示例三：指向字面量的变量比较" class="headerlink" title="示例三：指向字面量的变量比较"></a>示例三：指向字面量的变量比较</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="number">256</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b = <span class="number">256</span>  <span class="comment"># 和 a 指向同一字面量 256</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a <span class="keyword">is</span> b  <span class="comment"># 表明指向同一对象</span></span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a == b</span><br><span class="line"><span class="keyword">True</span></span><br><span class="line">&gt;&gt;&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="number">257</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b = <span class="number">257</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a == b</span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a <span class="keyword">is</span> b  <span class="comment"># 表明指向不同对象</span></span><br><span class="line"><span class="keyword">False</span></span><br><span class="line">&gt;&gt;&gt;</span><br></pre></td></tr></table></figure><p>通常来说，两个变量指向字面量，它们的比较应该使用 <code>==</code>，而非 <code>is</code>，否则就可能有类似上述示例中的困惑。</p><p>在 Python 的交互解释器中，把可能频繁使用的整数对象规定在范围 <code>[-5, 256]</code> 之间，当它们创建好后就会被缓存下来。但凡是需要再用到它们时，就会从缓存中取，而不是重新创建对象。</p><ul><li>当 <code>a</code> 和 <code>b</code> 都指向同一个字面量 <code>256</code> 时，<code>a is b</code> 返回 <code>True</code>。这是因为声明 <code>b = 256</code> 时，<code>256</code> 整数对象是从缓存中取的，而非重新创建，所以 <code>a</code> 和 <code>b</code> 指向同一个整数对象。</li><li>当 <code>a</code> 和 <code>b</code> 都指向同一个字面量 <code>257</code> 时，<code>a is b</code> 返回 <code>False</code>。这是因为声明 <code>b = 257</code> 时，<code>257</code> 整数对象没被缓存，是重新创建的，所以 <code>a</code> 和 <code>b</code> 指向不同的整数对象。</li></ul><p>同理，如果字面量是字符串，结果也类似。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="string">'python.org'</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b = <span class="string">'python.org'</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a <span class="keyword">is</span> b</span><br><span class="line"><span class="keyword">False</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a == b</span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a = <span class="string">'pythonorg'</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>b = <span class="string">'pythonorg'</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a <span class="keyword">is</span> b</span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>a == b</span><br><span class="line"><span class="keyword">True</span></span><br></pre></td></tr></table></figure><h2 id="Python-3-8-引入-is-比较字面量时报-SyntaxWarning"><a href="#Python-3-8-引入-is-比较字面量时报-SyntaxWarning" class="headerlink" title="Python 3.8 引入 is 比较字面量时报 SyntaxWarning"></a>Python 3.8 引入 <code>is</code> 比较字面量时报 SyntaxWarning</h2><p>鉴于使用 <code>is</code> 比较字面量其实是不正确的，在 Python 3.8 的 <a href="https://docs.python.org/3.8/whatsnew/3.8.html#changes-in-python-behavior" title="Python 3.8 release notes" target="_blank" rel="noopener">release notes</a> 中，引入如下内容：</p><blockquote><p>The compiler now produces a SyntaxWarning when identity checks (is and is not) are used with certain types of literals (e.g. strings, numbers). These can often work by accident in CPython, but are not guaranteed by the language spec. The warning advises users to use equality tests (== and !=) instead. (Contributed by Serhiy Storchaka in <a href="https://bugs.python.org/issue34850" title="bpo-34850" target="_blank" rel="noopener">bpo-34850</a>.)</p></blockquote><p>因此，当我们使用 <code>is</code> 去比较数字、字符串等字面量时，就会报 <code>SyntaxWarning</code>：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>x = <span class="number">200</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>x <span class="keyword">is</span> <span class="number">200</span></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">"&lt;stdin&gt;"</span>, line <span class="number">1</span>, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">SyntaxWarning: "is" with a literal. Did you mean "=="?</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>说了这么多，其实我们只需要记住如下两点：</p><ul><li>当要比较值是否相等时，请用 <code>==</code>。</li><li>当要比较是否是同一个对象时，请用 <code>is</code>。</li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h2&gt;&lt;p&gt;Python 的 “is” 和 “==” 想必大家都不陌生，我们在比较变量和字面量时常常用到它们，可是它们的区别在哪里？什么情况下该用 &lt;code&gt;is&lt;/code&gt;？什么情况下该用 &lt;code&gt;==&lt;/code&gt;？这成了不少人心中的困惑。&lt;/p&gt;
&lt;p&gt;当我们判断一个变量是否为 &lt;code&gt;None&lt;/code&gt; 时，通常会用 &lt;code&gt;is&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;a = &lt;span class=&quot;keyword&quot;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;a &lt;span class=&quot;keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;True&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;b = &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;b &lt;span class=&quot;keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;False&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;而当我们判断一个变量是否为字面量（比如某个数值）时，通常会用 &lt;code&gt;==&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;a = &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;a == &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;True&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;a == &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;False&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;要想解决上面的疑惑，我们首先需要搞明白 &lt;code&gt;is&lt;/code&gt; 和 &lt;code&gt;==&lt;/code&gt; 是什么。&lt;/p&gt;
    
    </summary>
    
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>Python 壹周刊 008</title>
    <link href="http://prodesire.cn/2020/01/26/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-008/"/>
    <id>http://prodesire.cn/2020/01/26/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-008/</id>
    <published>2020-01-26T13:32:50.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="新鲜事儿"><a href="#新鲜事儿" class="headerlink" title="新鲜事儿"></a>新鲜事儿</h2><p>本周没有新鲜事儿。</p><h2 id="好文共赏"><a href="#好文共赏" class="headerlink" title="好文共赏"></a>好文共赏</h2><p><a href="https://dev.to/educative/software-developer-trends-of-2020-and-beyond-5fj6" target="_blank" rel="noopener">2020 年后的软件开发人员趋势</a></p><p><a href="https://medium.com/better-programming/python-progress-bars-with-tqdm-by-example-ce98dbbc9697" target="_blank" rel="noopener">Python 进度条 tqdm 示例</a></p><p>进度条是程序开发中一个不可获取的功能，网上关于 Python 进度条库 tqdm 的示例说的并不友好，本文将深入浅出地带你如何使用 tqdm 实现进度条功能。</p><p><a href="https://adamj.eu/tech/2020/01/21/why-does-python-3-8-syntaxwarning-for-is-literal/" target="_blank" rel="noopener">为什么 Python 3.8 给 “is” 打印 SyntaxWarning？</a></p><a id="more"></a><p><a href="https://arxiv.org/pdf/2001.02491.pdf" target="_blank" rel="noopener">对比 Python、Go 和 C++ 的 N 皇后问题</a></p><p>Python 当前是机器学习领域的主导语言，但经常因执行某些任务的速度慢而受到批评。在本文中，我们使用著名的 N 皇后难题作为基准，来说明一旦使用 Numba 编译器进行编译，它就可以在执行速度上与 C 和 Go 竞争，同时还可以非常快速地制作原型。</p><p><a href="https://medium.com/kolonial-no-product-tech/codemodding-python-unittest-asserts-to-python-asserts-dbf4d1da8c0" target="_blank" rel="noopener">使用 codemod 将 unittest 断言转换为 Python 断言</a></p><p>大型代码库需要持续维护，但是更改分布在多个文件中的代码既费时又麻烦。本文展示了如何通过 codemod 借助抽象语法树来重构 Python 代码——相比于基本的正则和搜索替换，控制粒度要更细。</p><p><a href="https://realpython.com/arcade-python-game-framework/" target="_blank" rel="noopener">Python 游戏框架指南</a></p><p>在本循序渐进的教程中，您将学习如何使用 arcade，一种现代的 Python 框架，制作具有引人入胜的图形和声音的游戏。 Arcade 是针对 Python 3.6 及更高版本而构建的面向对象的库，为您提供了一套现代的工具，可提供出色的 Python 游戏体验。</p><p><a href="https://nkanaev.github.io/posts/polyglot/" target="_blank" rel="noopener">编写多语言脚本</a></p><p>Python 和 Ruby 的语法有些相似，您能否想出一个在两种语言中均有效的程序？</p><p><a href="https://pbpython.com/effective-matplotlib.html" target="_blank" rel="noopener">高效使用 MatPlotlib</a></p><p>我已经花了一些时间来学习 Python 可视化的一些工具以及如何将其与 matplotlib 一起使用，我开始将 matplotlib 视为必不可少的工具。 这篇文章将展示我如何使用 matplotlib 并为用户入门提供一些建议。</p><p><a href="https://realpython.com/python-scipy-cluster-optimize/" target="_blank" rel="noopener">科学计算 Python：使用 SciPy 进行优化</a></p><p>了解 SciPy 生态系统及与 SciPy 库的区别。您将学习如何使用 Anaconda 或 pip 安装 SciPy，并查看其一些模块。 然后您将重点关注使用 SciPy 中的群集和优化功能的示例。</p><p><a href="http://hondu.co/blog/open-and-python" target="_blank" rel="noopener">open() 和 CPython 的意外结果</a></p><p>滥用 Python 的 open()， 以及 CPython 的 GC 和 UNIX 语言的互相作用，可能会导致意外结果。</p><p><a href="https://www.b-list.org/weblog/2020/jan/20/fun/" target="_blank" rel="noopener">找点 Python 的乐子</a></p><p>编写混乱的代码，除了可以获得乐趣，也是绝佳的学习体验。</p><p><a href="https://sourcery.ai/blog/five-refactoring-tips/" target="_blank" rel="noopener">改善代码的 5 个重构建议</a></p><p><a href="https://blog.ionelmc.ro/2020/01/20/is-there-anything-safe-in-python/" target="_blank" rel="noopener">编写安全的 repr()</a></p><h2 id="赞视频"><a href="#赞视频" class="headerlink" title="赞视频"></a>赞视频</h2><p><a href="https://realpython.com/courses/python-data-types/" target="_blank" rel="noopener">Python 中的基本数据类型</a></p><p>在本课程中，您将学习 Python 内置的基本数据类型，例如数字、字符串和布尔值，以及 Python 内置函数的概览。</p><h2 id="酷开源"><a href="#酷开源" class="headerlink" title="酷开源"></a>酷开源</h2><p><a href="https://pypistats.org/" target="_blank" rel="noopener">pypistats</a></p><p>PyPI 下载统计。</p><p><a href="https://github.com/getsentry/sentry" target="_blank" rel="noopener">Sentry</a></p><p>实时日志记录和聚合服务端。</p><p><a href="https://github.com/celery/celery" target="_blank" rel="noopener">celery</a></p><p>基于分布式消息的异步任务队列。</p><p><a href="https://github.com/saltstack/salt" target="_blank" rel="noopener">SaltStack</a></p><p>基础设施自动化和管理系统。</p><p><a href="https://github.com/paramiko/paramiko" target="_blank" rel="noopener">Paramiko</a></p><p>SSHv2 协议的 Python (2.6+, 3.3+) 实现，同时提供客户端和服务端功能。</p><p><a href="https://github.com/grantjenks/python-diskcache" target="_blank" rel="noopener">DiskCache</a></p><p>使用 SQLite 和文件作为后端缓存，比 memcached 和 redis 的查询都要快。</p><p><a href="https://github.com/tmux-python/tmuxp" target="_blank" rel="noopener">tmuxp</a></p><p>💻 基于 libtmux 的 tmux 会话管理器。</p><p><a href="https://github.com/fsistemas/sql2json" target="_blank" rel="noopener">sql2json</a></p><p>运行查询并将结果转换为 json 的 Python 工具。</p><p><a href="https://github.com/ionelmc/python-hunter/" target="_blank" rel="noopener">python-hunter</a></p><p>一个灵活的代码追踪工具。</p><p><a href="https://github.com/pschanely/CrossHair" target="_blank" rel="noopener">CrossHair</a></p><p>用于 Python 的静态分析工具，模糊了测试系统和类型系统之间的界限。</p><p><a href="https://github.com/lucashadfield/speck" target="_blank" rel="noopener">speck</a></p><p>将图像渲染为一组连续的（水平或垂直）像素线。</p><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;新鲜事儿&quot;&gt;&lt;a href=&quot;#新鲜事儿&quot; class=&quot;headerlink&quot; title=&quot;新鲜事儿&quot;&gt;&lt;/a&gt;新鲜事儿&lt;/h2&gt;&lt;p&gt;本周没有新鲜事儿。&lt;/p&gt;
&lt;h2 id=&quot;好文共赏&quot;&gt;&lt;a href=&quot;#好文共赏&quot; class=&quot;headerlink&quot; title=&quot;好文共赏&quot;&gt;&lt;/a&gt;好文共赏&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://dev.to/educative/software-developer-trends-of-2020-and-beyond-5fj6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2020 年后的软件开发人员趋势&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/better-programming/python-progress-bars-with-tqdm-by-example-ce98dbbc9697&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Python 进度条 tqdm 示例&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;进度条是程序开发中一个不可获取的功能，网上关于 Python 进度条库 tqdm 的示例说的并不友好，本文将深入浅出地带你如何使用 tqdm 实现进度条功能。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://adamj.eu/tech/2020/01/21/why-does-python-3-8-syntaxwarning-for-is-literal/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;为什么 Python 3.8 给 “is” 打印 SyntaxWarning？&lt;/a&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/categories/Python/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/tags/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
  </entry>
  
  <entry>
    <title>Python 命令行之旅：使用 fire 实现 git 命令</title>
    <link href="http://prodesire.cn/2020/01/12/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E4%BD%BF%E7%94%A8-fire-%E5%AE%9E%E7%8E%B0-git-%E5%91%BD%E4%BB%A4/"/>
    <id>http://prodesire.cn/2020/01/12/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E4%BD%BF%E7%94%A8-fire-%E5%AE%9E%E7%8E%B0-git-%E5%91%BD%E4%BB%A4/</id>
    <published>2020-01-12T09:10:07.000Z</published>
    <updated>2020-07-24T15:00:04.356Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>在前面三篇介绍 <code>fire</code> 的文章中，我们全面了解了 <code>fire</code> 强大而不失简洁的能力。按照惯例，我们要像使用 <code>argparse</code>、<code>docopt</code> 和 <code>click</code> 一样使用 <code>fire</code> 来实现 git 命令。</p><p>本文的关注点并不在 <code>git</code> 的各种命令是如何实现的，而是怎么使用 <code>fire</code> 去打造一个实用命令行程序，代码结构是怎样的。因此，和 <code>git</code> 相关的操作，将会使用 <code>gitpython</code> 库来简单实现。</p><p>为了让没读过 <code>使用 xxx 实现 git 命令</code>（<code>xxx</code> 指 <code>argparse</code>、<code>docopt</code> 和 <code>click</code>） 的小伙伴也能读明白本文，我们仍会对 <code>git</code> 常用命令和 <code>gitpython</code> 做一个简单介绍。</p><a id="more"></a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">本系列文章默认使用 Python 3 作为解释器进行讲解。</span><br><span class="line">若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~</span><br></pre></td></tr></table></figure><h2 id="二、git-常用命令"><a href="#二、git-常用命令" class="headerlink" title="二、git 常用命令"></a>二、git 常用命令</h2><p>当你写好一段代码或增删一些文件后，会用如下命令查看文件状态：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br></pre></td></tr></table></figure><p>确认文件状态后，会用如下命令将的一个或多个文件（夹）添加到暂存区：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git add [pathspec [pathspec ...]]</span><br></pre></td></tr></table></figure><p>然后使用如下命令提交信息：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git commit -m <span class="string">"your commit message"</span></span><br></pre></td></tr></table></figure><p>最后使用如下命令将提交推送到远程仓库：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push</span><br></pre></td></tr></table></figure><p>我们将使用 <code>fire</code> 和 <code>gitpython</code> 库来实现这 4 个子命令。</p><h2 id="三、关于-gitpython"><a href="#三、关于-gitpython" class="headerlink" title="三、关于 gitpython"></a>三、关于 gitpython</h2><p><a href="https://gitpython.readthedocs.io/en/stable/intro.html" target="_blank" rel="noopener">gitpython</a> 是一个和 <code>git</code> 仓库交互的 Python 第三方库。<br>我们将借用它的能力来实现真正的 <code>git</code> 逻辑。</p><p>安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install gitpython</span><br></pre></td></tr></table></figure><h2 id="四、思考"><a href="#四、思考" class="headerlink" title="四、思考"></a>四、思考</h2><p>在实现前，我们不妨先思考下会用到 <code>fire</code> 的哪些功能？整个程序的结构是怎样的？</p><p><strong>fire</strong></p><p><code>git</code> 的 4 个子命令的实现其实对应于四个函数，我们可以都放到一个类中，实现四个实例方法。<br>而对于 <code>git add</code> 命令，需要接受任意个参数，在实例方法中用 <code>*pathspecs</code> 参数来表达。<br>对于 <code>git commit</code> 命令，需要接受 <code>-m</code> 选项，在实例方法中用 <code>m</code> 参数来表达。</p><p><strong>程序结构</strong></p><p>程序结构上：</p><ul><li>实例化 <code>Git</code> 对象，供全局使用</li><li>在 <code>GitCli</code> 类中定义四个命令对应的实例方法 <code>status</code>、<code>add</code>、<code>commit</code>、<code>push</code></li></ul><p>则基本结构如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"><span class="keyword">from</span> git.cmd <span class="keyword">import</span> Git</span><br><span class="line"></span><br><span class="line">git = Git(os.getcwd())</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GitCli</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">status</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        处理 status 命令</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(self, *pathspecs)</span>:</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        处理 add 命令</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">commit</span><span class="params">(self, m)</span>:</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        处理 -m &lt;msg&gt; 命令</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">push</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        处理 push 命令</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    fire.Fire(GitCli())</span><br></pre></td></tr></table></figure><p>下面我们将一步步地实现我们的 <code>git</code> 程序。</p><h2 id="五、实现"><a href="#五、实现" class="headerlink" title="五、实现"></a>五、实现</h2><p>假定我们在 <a href="https://github.com/HelloGitHub-Team/Article/blob/master/contents/Python/cmdline/fire-git.py" target="_blank" rel="noopener">fire-git.py</a> 文件中实现我们的 <code>git</code> 程序。</p><h3 id="5-1-status-子命令"><a href="#5-1-status-子命令" class="headerlink" title="5.1 status 子命令"></a>5.1 status 子命令</h3><p><code>status</code> 子命令不接受任何参数和选项，因此 <code>status</code> 方法无需任何入参。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GitCli</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">status</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        处理 status 命令</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        cmd = [<span class="string">'git'</span>, <span class="string">'status'</span>]</span><br><span class="line">        output = git.execute(cmd)</span><br><span class="line">        <span class="keyword">return</span> output</span><br></pre></td></tr></table></figure><p>不难看出，我们最后调用了真正的 <code>git status</code> 来实现，并打印了输出。</p><h3 id="5-2-add-子命令"><a href="#5-2-add-子命令" class="headerlink" title="5.2 add 子命令"></a>5.2 add 子命令</h3><p><code>add</code> 子命令相对于 <code>status</code> 子命令，需要接受任意个 pathspec 参数，因此 <code>add</code> 方法需要增加 <code>*pathspecs</code> 入参。<br>fire 最终传入的是一个元组，我们需要将其转换成 list 以便后续处理。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GitCli</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(self, *pathspecs)</span>:</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        处理 add 命令</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        cmd = [<span class="string">'git'</span>, <span class="string">'add'</span>] + list(pathspecs)</span><br><span class="line">        output = git.execute(cmd)</span><br><span class="line">        <span class="keyword">return</span> output</span><br></pre></td></tr></table></figure><p>当我们执行 <code>python3 fire-git.py add --help</code> 时，结果如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">INFO: Showing help with the command &apos;fire-git.py add -- --help&apos;.</span><br><span class="line"></span><br><span class="line">NAME</span><br><span class="line">    fire-git.py add - 处理 add 命令</span><br><span class="line"></span><br><span class="line">SYNOPSIS</span><br><span class="line">    fire-git.py add [PATHSPECS]...</span><br><span class="line"></span><br><span class="line">DESCRIPTION</span><br><span class="line">    处理 add 命令</span><br><span class="line"></span><br><span class="line">POSITIONAL ARGUMENTS</span><br><span class="line">    PATHSPECS</span><br></pre></td></tr></table></figure><h3 id="5-3-commit-子命令"><a href="#5-3-commit-子命令" class="headerlink" title="5.3 commit 子命令"></a>5.3 commit 子命令</h3><p><code>commit</code> 子命令相对于 <code>status</code> 子命令，需要接受 <code>-m</code> 选项，因此 <code>commit</code> 方法需要增加 <code>m</code> 入参。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GitCli</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">commit</span><span class="params">(self, m)</span>:</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        处理 -m &lt;msg&gt; 命令</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        cmd = [<span class="string">'git'</span>, <span class="string">'commit'</span>, <span class="string">'-m'</span>, m]</span><br><span class="line">        output = git.execute(cmd)</span><br><span class="line">        <span class="keyword">return</span> output</span><br></pre></td></tr></table></figure><h3 id="5-4-push-子命令"><a href="#5-4-push-子命令" class="headerlink" title="5.4 push 子命令"></a>5.4 push 子命令</h3><p><code>push</code> 子命令同 <code>status</code> 子命令一样，不接受任何参数和选项，因此 <code>push</code> 方法无需任何入参。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GitCli</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">push</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        处理 push 命令</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        cmd = [<span class="string">'git'</span>, <span class="string">'push'</span>]</span><br><span class="line">        output = git.execute(cmd)</span><br><span class="line">        <span class="keyword">return</span> output</span><br></pre></td></tr></table></figure><p>至此，我们就实现了一个简单的 <code>git</code> 命令行，使用 <code>python fire-git.py status</code> 便可查询项目状态。</p><p>非常方便的是，每个命令函数的 <code>docstring</code> 都将作为这个命令的帮助信息，因此，当我们执行 <code>python3 fire-git.py --help</code> 会自动生成如下帮助内容：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">INFO: Showing help with the command &apos;fire-git.py -- --help&apos;.</span><br><span class="line"></span><br><span class="line">NAME</span><br><span class="line">    fire-git.py</span><br><span class="line"></span><br><span class="line">SYNOPSIS</span><br><span class="line">    fire-git.py COMMAND</span><br><span class="line"></span><br><span class="line">COMMANDS</span><br><span class="line">    COMMAND is one of the following:</span><br><span class="line"></span><br><span class="line">     add</span><br><span class="line">       处理 add 命令</span><br><span class="line"></span><br><span class="line">     commit</span><br><span class="line">       处理 -m &lt;msg&gt; 命令</span><br><span class="line"></span><br><span class="line">     push</span><br><span class="line">       处理 push 命令</span><br><span class="line"></span><br><span class="line">     status</span><br><span class="line">       处理 status 命令</span><br></pre></td></tr></table></figure><p>想看整个源码，请戳 <a href="https://github.com/HelloGitHub-Team/Article/blob/master/contents/Python/cmdline/fire-git.py" target="_blank" rel="noopener">fire-git.py</a> 。</p><h2 id="六、小结"><a href="#六、小结" class="headerlink" title="六、小结"></a>六、小结</h2><p>本文简单介绍了日常工作中常用的 <code>git</code> 命令，然后提出实现它的思路，最终一步步地使用 <code>fire</code> 和 <code>gitpython</code> 实现了 <code>git</code> 程序。</p><p>对比 <code>argparse</code>、<code>docopt</code> 和 <code>click</code> 的实现版本，你会发现使用 <code>fire</code> 来实现是最简单的：</p><ul><li>相较于 <code>argparse</code>，子解析器、参数类型什么的统统不需要关心</li><li>相较于 <code>docopt</code>，参数解析和命令调用处理也不需要关心</li><li>相较于 <code>click</code>，装饰器所定义的命令行参数信息也必须要关心</li></ul><p>无疑，<code>fire</code> 把能简化的都简化了，简直就是懒人福音。</p><p>关于 <code>fire</code> 的讲解将告一段落，回顾下 <code>fire</code> 的至简之道，你会深爱上它。这也体现出了 Python 之美。</p><p>现在，你已学会了四个特点各异的主流命令行解析库的使用了，再也不需要为命令行程序的实现而烦恼了。</p><p>什么，你为要使用哪一个库而发愁？在下一篇也是最后一篇文章中，我们将对这些库做一个横向对比，以对什么场景下使用什么样的命令行库了然于胸~</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ul><li><a href="/2019/08/13/Python-命令行之旅：初探-argparse/" title="Python 命令行之旅：初探 argparse">Python 命令行之旅：初探 argparse</a></li><li><a href="/2019/08/20/Python-命令行之旅：深入-argparse（一）/" title="Python 命令行之旅：深入 argparse（一）">Python 命令行之旅：深入 argparse（一）</a></li><li><a href="/2019/08/27/Python-命令行之旅：深入-argparse（二）/" title="Python 命令行之旅：深入 argparse（二）">Python 命令行之旅：深入 argparse（二）</a></li><li><a href="/2019/09/04/Python-命令行之旅：使用-argparse-实现-git-命令/" title="Python 命令行之旅：使用 argparse 实现 git 命令">Python 命令行之旅：使用 argparse 实现 git 命令</a></li><li><a href="/2019/10/09/Python-命令行之旅：初探-docopt/" title="Python 命令行之旅：初探 docopt">Python 命令行之旅：初探 docopt</a></li><li><a href="/2019/10/15/Python-命令行之旅：深入-docopt/" title="Python 命令行之旅：深入 docopt">Python 命令行之旅：深入 docopt</a></li><li><a href="/2019/10/30/Python-命令行之旅：使用-docopt-实现-git-命令/" title="Python 命令行之旅：使用 docopt 实现 git 命令">Python 命令行之旅：使用 docopt 实现 git 命令</a></li><li><a href="/2019/11/04/Python-命令行之旅：初探-click/" title="Python 命令行之旅：初探 click">Python 命令行之旅：初探 click</a></li><li><a href="/2019/11/11/Python-命令行之旅：深入-click（一）/" title="Python 命令行之旅：深入 click（一）">Python 命令行之旅：深入 click（一）</a></li><li><a href="/2019/11/17/Python-命令行之旅：深入-click（二）/" title="Python 命令行之旅：深入 click（二）">Python 命令行之旅：深入 click（二）</a></li><li><a href="/2019/11/25/Python-命令行之旅：深入-click（三）/" title="Python 命令行之旅：深入 click（三）">Python 命令行之旅：深入 click（三）</a></li><li><a href="/2019/12/09/Python-命令行之旅：深入-click（四）/" title="Python 命令行之旅：深入 click（四）">Python 命令行之旅：深入 click（四）</a></li><li><a href="/2019/12/14/Python-命令行之旅：使用-click-实现-git-命令/" title="Python 命令行之旅：使用 click 实现 git 命令">Python 命令行之旅：使用 click 实现 git 命令</a></li><li><a href="/2019/12/22/Python-命令行之旅：初探-fire/" title="Python 命令行之旅：初探 fire">Python 命令行之旅：初探 fire</a></li><li><a href="/2019/12/29/Python-命令行之旅：深入-fire（一）/" title="Python 命令行之旅：深入 fire（一）">Python 命令行之旅：深入 fire（一）</a></li><li><a href="/2020/01/05/Python-命令行之旅：深入-fire（二）/" title="Python 命令行之旅：深入 fire（二）">Python 命令行之旅：深入 fire（二）</a></li><li><a href="/2020/01/12/Python-命令行之旅：使用-fire-实现-git-命令/" title="Python 命令行之旅：使用 fire 实现 git 命令">Python 命令行之旅：使用 fire 实现 git 命令</a></li><li><a href="/2020/02/09/Python-命令行之旅：argparse、docopt、click-和-fire-总结篇/" title="Python 命令行之旅：argparse、docopt、click 和 fire 总结篇">Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</a></li><li><a href="/2020/07/02/Python-命令行大乱斗/" title="Python 命令行大乱斗">Python 命令行大乱斗</a></li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;在前面三篇介绍 &lt;code&gt;fire&lt;/code&gt; 的文章中，我们全面了解了 &lt;code&gt;fire&lt;/code&gt; 强大而不失简洁的能力。按照惯例，我们要像使用 &lt;code&gt;argparse&lt;/code&gt;、&lt;code&gt;docopt&lt;/code&gt; 和 &lt;code&gt;click&lt;/code&gt; 一样使用 &lt;code&gt;fire&lt;/code&gt; 来实现 git 命令。&lt;/p&gt;
&lt;p&gt;本文的关注点并不在 &lt;code&gt;git&lt;/code&gt; 的各种命令是如何实现的，而是怎么使用 &lt;code&gt;fire&lt;/code&gt; 去打造一个实用命令行程序，代码结构是怎样的。因此，和 &lt;code&gt;git&lt;/code&gt; 相关的操作，将会使用 &lt;code&gt;gitpython&lt;/code&gt; 库来简单实现。&lt;/p&gt;
&lt;p&gt;为了让没读过 &lt;code&gt;使用 xxx 实现 git 命令&lt;/code&gt;（&lt;code&gt;xxx&lt;/code&gt; 指 &lt;code&gt;argparse&lt;/code&gt;、&lt;code&gt;docopt&lt;/code&gt; 和 &lt;code&gt;click&lt;/code&gt;） 的小伙伴也能读明白本文，我们仍会对 &lt;code&gt;git&lt;/code&gt; 常用命令和 &lt;code&gt;gitpython&lt;/code&gt; 做一个简单介绍。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/categories/Python/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
      <category term="fire" scheme="http://prodesire.cn/tags/fire/"/>
    
  </entry>
  
  <entry>
    <title>Python 壹周刊 007</title>
    <link href="http://prodesire.cn/2020/01/08/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-007/"/>
    <id>http://prodesire.cn/2020/01/08/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-007/</id>
    <published>2020-01-08T09:07:57.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="新鲜事儿"><a href="#新鲜事儿" class="headerlink" title="新鲜事儿"></a>新鲜事儿</h2><p><a href="https://pythonclock.org/?2020" target="_blank" rel="noopener">Python 2 已退休</a></p><p><a href="https://www.python.org/dev/peps/pep-8101/" target="_blank" rel="noopener">老爹 Guido van Rossum 退出 Python 指导委员会</a></p><a id="more"></a><h2 id="好文共赏"><a href="#好文共赏" class="headerlink" title="好文共赏"></a>好文共赏</h2><p><a href="https://kunigami.blog/2019/12/26/python-type-hints/" target="_blank" rel="noopener">Python 类型注解</a></p><p>本文将全面介绍 mypy，通过许多示例演示了这种类型检查器的语法和功能。</p><p><a href="http://www.flake8rules.com/" target="_blank" rel="noopener">Flask8 规则</a></p><p>Flake8 中的所有规则的说明和示例。</p><p><a href="https://blog.nicco.io/2020/01/01/rust-in-python-made-easy" target="_blank" rel="noopener">在 Python 中使用 Rust 变得简单</a></p><p>对于需要性能提升的计算密集型任务，可以由 Rust 来实现逻辑，然后在 Python 中调用。本文将介绍如何实现这个过程。</p><p><a href="https://nicholasfarrow.com/Creating-a-Moon-Animation-Using-NASA-Images-and-Python/" target="_blank" rel="noopener">借助 NASA 图片和 Python 制作月亮视频</a></p><p>手把手教你如果通过 Python 和数张 NASA 的月亮图片制作月亮视频。</p><p><a href="https://martinheinz.dev/blog/13" target="_blank" rel="noopener">让 Python 程序闪电般迅速</a></p><p>讨厌 Python 的人总是说，他们不想使用它的原因之一是它很慢。 嗯，特定的程序（无论使用何种编程语言）是快还是慢，很大程度上取决于编写该程序的开发人员以及编写优化的快速的程序的技能和能力。 因此，让我们证明一些人是错误的，让我们看看如何改善 Python 程序的性能并使它们真正更快！</p><p><a href="https://iximiuz.com/en/posts/flask-gevent-tutorial/" target="_blank" rel="noopener">如何将 Flask 与 gevent 一起使用（uWSGI 和 Gunicorn 版本）</a></p><p>创建异步 Flask 应用程序，并在 Nginx 反向代理后面使用 uWSGI 或 Gunicorn 来运行它。</p><p><a href="https://medium.com/@wolfv/robot-development-with-jupyter-ddae16d4e688" target="_blank" rel="noopener">使用 Jupyter 开发机器人</a></p><p>这篇文章展示了 Jupyter 生态系统中可用的工具，在 Jupyter Notebooks 中构建高级可视化并使用 Voilá 转换为独立的 Web 应用程序，以及如何将这些应用程序部署到机器人云中。</p><p><a href="https://lucumr.pocoo.org/2020/1/1/async-pressure/" target="_blank" rel="noopener">我没有感受到 async 的压力</a></p><p>如今异步风靡一时，异步 Python、异步 Rust、Go、Node、.NET，选择您喜欢的生态系统，它有些异步操作。这种异步操作的工作原理在很大程度上取决于语言的生态系统和运行时，但总体而言，它具有一些不错的好处。这使事情变得非常简单：异步等待（await）可能需要一些时间才能完成的操作。它是如此简单，以至于创造了无数新的方法来“打击”人。我要讨论的是在系统超载之前您还没有意识到自己可能采坑的情况，也就是背压管理。在协议设计中，的一个相关术语叫做流量控制。</p><p><a href="https://orbifold.xyz/zen-of-python.html" target="_blank" rel="noopener">Python 之禅的思考</a></p><p><a href="https://realpython.com/python-timer/" target="_blank" rel="noopener">Python 计时器功能：监视代码的三种方法</a></p><p>了解如何使用 Python 计时器功能来监视程序的运行速度。您将使用类、上下文管理器和装饰器来测量程序的运行时间。 您将了解每种方法的优点以及在特定情况下可以使用的方法。</p><p><a href="https://lucumr.pocoo.org/2019/12/28/open-source-migrates/" target="_blank" rel="noopener">开源迁移的困扰</a></p><p>Flask 的创建者介绍了 Python 2 到 3 的迁移以及 Python 社区如何处理过渡。有趣的内容！</p><h2 id="赞视频"><a href="#赞视频" class="headerlink" title="赞视频"></a>赞视频</h2><p><a href="https://www.youtube.com/watch?v=7kn7NtlV6g0" target="_blank" rel="noopener">Python 老爹在牛津联盟的访谈</a></p><p><a href="https://www.youtube.com/watch?v=x58W9A2lnQc" target="_blank" rel="noopener">Numba 让 Python 快上 1000 倍!</a></p><p>在此视频中，我介绍了您需要了解的有关 Numba 的最低要求，Numba 是针对 Python 和 Numpy 子集的即时编译器。 该视频的前半部分做了基本介绍，并着重介绍了人们在使用 Numba 时常犯的一些错误。后半部分提出了一个现实世界中的模拟问题，在单线程和多线程情况下，使用 Numba 最多可加速 1000 倍。最后给出一个“阅读清单”作为结尾，以了解有关 Numba 的更多信息。</p><h2 id="酷开源"><a href="#酷开源" class="headerlink" title="酷开源"></a>酷开源</h2><p><a href="https://github.com/mirumee/saleor" target="_blank" rel="noopener">Saleor</a></p><p>使用 Python、GraphQL、Django 和 ReactJS 构建的模块化、高性能的电子商务网站。</p><p><a href="https://github.com/coleifer/peewee" target="_blank" rel="noopener">Peewee</a></p><p>一个小巧、富有表现力的 ORM —— 支持 PostgreSQL、MySQL 和 SQLite。</p><p><a href="https://github.com/python-poetry/poetry" target="_blank" rel="noopener">Poetry</a></p><p>让 Python 的依赖项管理和打包变得容易。</p><p><a href="https://github.com/sobolevn/django-split-settings" target="_blank" rel="noopener">django-split-settings</a></p><p>将 Django 设置分散到多个文件和目录中，能够轻松覆盖和修改设置。</p><p><a href="https://github.com/python-gino/gino" target="_blank" rel="noopener">GINO</a></p><p>GINO 递归定义为 GINO Is Not ORM，是一个基于 asyncio 和 SQLAlchemy core 的轻量级异步 Python ORM 框架，目前（2020 年初）仅支持 asyncpg 一种引擎。</p><p><a href="https://github.com/qutip/qutip" target="_blank" rel="noopener">QuTiP</a></p><p>Python 中的量子工具箱。</p><p><a href="https://github.com/obspy/obspy" target="_blank" rel="noopener">ObsPy</a></p><p>用于处理地震数据的 Python 框架。</p><p><a href="https://github.com/kornia/kornia" target="_blank" rel="noopener">Kornia</a></p><p>PyTorch 的开源可区分计算机视觉库。</p><p><a href="https://github.com/tiangolo/typer" target="_blank" rel="noopener">Typer</a></p><p>基于 Python 类型注解的 CLI 库，能够简单地创建 CLI 程序。</p><p><a href="https://github.com/knowsuchagency/klaxon" target="_blank" rel="noopener">klaxon</a></p><p>从终端或 Python 程序中发送 Mac OS 通知。</p><p><a href="https://github.com/kkroening/ffmpeg-python" target="_blank" rel="noopener">ffmpeg-python</a></p><p>FFmpeg 的 Python 绑定，支持复杂的过滤</p><p><a href="https://github.com/anmspro/Traffic-Signal-Violation-Detection-System" target="_blank" rel="noopener">Traffic-Signal-Violation-Detection-System</a></p><p>使用 YOLOv3 和 Tkinter 实现的基于计算机视觉的交通信号违规检测系统。</p><p><a href="https://github.com/PydPiper/pylightxl" target="_blank" rel="noopener">pylightxl</a></p><p>轻量级、零依赖、最简功能的 excel 读/写 Python 库。</p><p><a href="https://github.com/haroonawanofficial/XSS-Finder" target="_blank" rel="noopener">XSS Finder</a></p><p>大型、高级的跨站点脚本扫描程序。</p><p><a href="https://github.com/warner/magic-wormhole" target="_blank" rel="noopener">Magic Wormhole</a></p><p>安全地将内容从一台计算机转移到另一台计算机上。</p><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;新鲜事儿&quot;&gt;&lt;a href=&quot;#新鲜事儿&quot; class=&quot;headerlink&quot; title=&quot;新鲜事儿&quot;&gt;&lt;/a&gt;新鲜事儿&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://pythonclock.org/?2020&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Python 2 已退休&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.python.org/dev/peps/pep-8101/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;老爹 Guido van Rossum 退出 Python 指导委员会&lt;/a&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/categories/Python/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/tags/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
  </entry>
  
  <entry>
    <title>Python 命令行之旅：深入 fire（二）</title>
    <link href="http://prodesire.cn/2020/01/05/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E6%B7%B1%E5%85%A5-fire%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
    <id>http://prodesire.cn/2020/01/05/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E6%B7%B1%E5%85%A5-fire%EF%BC%88%E4%BA%8C%EF%BC%89/</id>
    <published>2020-01-05T15:03:40.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>在上一篇文章中我们介绍了 <code>fire</code> 的子命令、嵌套命令和属性访问等内容，今天我们将继续深入了解 <code>fire</code> 的其他功能。</p><a id="more"></a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">本系列文章默认使用 Python 3 作为解释器进行讲解。</span><br><span class="line">若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~</span><br></pre></td></tr></table></figure><h2 id="二、功能"><a href="#二、功能" class="headerlink" title="二、功能"></a>二、功能</h2><h3 id="2-1-最简命令实现"><a href="#2-1-最简命令实现" class="headerlink" title="2.1 最简命令实现"></a>2.1 最简命令实现</h3><p>在上一节中，我们介绍了只要定义一个函数就可以实现命令行程序。比如：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">english</span><span class="params">()</span>:</span></span><br><span class="line">  <span class="keyword">return</span> <span class="string">'Hello, fire!'</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">chinese</span><span class="params">()</span>:</span></span><br><span class="line">  <span class="keyword">return</span> <span class="string">'你好，fire！'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire()</span><br></pre></td></tr></table></figure><p>但这还不是最简单的实现方式，<code>fire</code> 甚至允许你通过定义变量的方式来实现命令行！<br>上面的例子可以写成下面这种形式：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line">english = <span class="string">'Hello, fire!'</span></span><br><span class="line">chinese = <span class="string">'你好，fire！'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire()</span><br></pre></td></tr></table></figure><h3 id="2-2-链式调用"><a href="#2-2-链式调用" class="headerlink" title="2.2 链式调用"></a>2.2 链式调用</h3><p>在 <code>Fire CLI</code> 中，你可以通过链式调用不断地对上一个结果进行处理。</p><p>想做到这一点也很简单，就是在实例方法中返回 <code>self</code> 即可。</p><p>在下面的示例中，我们实现了一个简单的四则运算命令，可链式调用 <code>add</code>、<code>sub</code>、<code>mul</code> 和 <code>div</code>。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Calculator</span>:</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">    self.result = <span class="number">0</span></span><br><span class="line">    self.express = <span class="string">'0'</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">__str__</span><span class="params">(self)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">f'<span class="subst">&#123;self.express&#125;</span> = <span class="subst">&#123;self.result&#125;</span>'</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(self, x)</span>:</span></span><br><span class="line">    self.result += x</span><br><span class="line">    self.express = <span class="string">f'<span class="subst">&#123;self.express&#125;</span>+<span class="subst">&#123;x&#125;</span>'</span></span><br><span class="line">    <span class="keyword">return</span> self</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">sub</span><span class="params">(self, x)</span>:</span></span><br><span class="line">    self.result -= x</span><br><span class="line">    self.express = <span class="string">f'<span class="subst">&#123;self.express&#125;</span>-<span class="subst">&#123;x&#125;</span>'</span></span><br><span class="line">    <span class="keyword">return</span> self</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">mul</span><span class="params">(self, x)</span>:</span></span><br><span class="line">    self.result *= x</span><br><span class="line">    self.express = <span class="string">f'(<span class="subst">&#123;self.express&#125;</span>)*<span class="subst">&#123;x&#125;</span>'</span></span><br><span class="line">    <span class="keyword">return</span> self</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">div</span><span class="params">(self, x)</span>:</span></span><br><span class="line">    self.result /= x</span><br><span class="line">    self.express = <span class="string">f'(<span class="subst">&#123;self.express&#125;</span>)/<span class="subst">&#123;x&#125;</span>'</span></span><br><span class="line">    <span class="keyword">return</span> self</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire(Calculator)</span><br></pre></td></tr></table></figure><p>上述代码中的 <code>add</code>、<code>sub</code>、<code>mul</code>、<code>div</code> 分别对应加、减、乘、除的逻辑，每个方法都接受 <code>x</code> 参数作为参与运算的数字，返回值均为 <code>self</code>，这样就可以无限次地链式调用。在命令行中链式调用结束后，会最终调用到 <code>__str__</code> 方法将结果打印出来。</p><p>其中，<code>__str__</code> 在 <code>fire</code> 中用来完成自定义序列化。如果不提供这个方法，在链式调用完成后将会打印帮助内容。</p><p>比如，我们可以这么调用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ python calculator.py add 1 sub 2 mul 3 div 4</span><br><span class="line">((+1-2)*3)/4 = -0.75</span><br><span class="line"></span><br><span class="line">$ python calculator.py add 1 sub 2 mul 3 div 4 add 4 sub 3 mul 2 div 1</span><br><span class="line">((((0+1-2)*3)/4+4-3)*2)/1 = 0.5</span><br></pre></td></tr></table></figure><h3 id="2-3-位置参数和选项参数"><a href="#2-3-位置参数和选项参数" class="headerlink" title="2.3 位置参数和选项参数"></a>2.3 位置参数和选项参数</h3><p>通过前面的介绍我们也都清楚了在 <code>fire</code> 中不必显式的定义位置参数或选项参数。</p><p>通过下面的例子，我们将细化两类参数的使用：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Building</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, name, stories=<span class="number">1</span>)</span>:</span></span><br><span class="line">    self.name = name</span><br><span class="line">    self.stories = stories</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">__str__</span><span class="params">(self)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">f'name: <span class="subst">&#123;self.name&#125;</span>, stories: <span class="subst">&#123;self.stories&#125;</span>'</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">climb_stairs</span><span class="params">(self, stairs_per_story=<span class="number">10</span>)</span>:</span></span><br><span class="line">    <span class="keyword">yield</span> self.name</span><br><span class="line">    <span class="keyword">for</span> story <span class="keyword">in</span> range(self.stories):</span><br><span class="line">      <span class="keyword">for</span> stair <span class="keyword">in</span> range(<span class="number">1</span>, stairs_per_story):</span><br><span class="line">        <span class="keyword">yield</span> stair</span><br><span class="line">      <span class="keyword">yield</span> <span class="string">'Phew!'</span></span><br><span class="line">    <span class="keyword">yield</span> <span class="string">'Done!'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire(Building)</span><br></pre></td></tr></table></figure><ul><li>构造函数中定义的参数（如 <code>name</code> 和 <code>stories</code>）在命令行中仅为选项参数（如 <code>--name</code> 和 <code>--stories</code>）。我们可以这么调用：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py --name=<span class="string">"Sherrerd Hall"</span> --stories=3</span><br></pre></td></tr></table></figure><ul><li>构造函数中定义的参数可在命令中放于任意位置。比如下面两个调用都是可以的：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py --name=<span class="string">"Sherrerd Hall"</span> climb-stairs --stairs-per-story 10</span><br><span class="line">$ python example.py climb-stairs --stairs-per-story 10 --name=<span class="string">"Sherrerd Hall"</span></span><br></pre></td></tr></table></figure><ul><li>构造函数和普通方法中定义的默认参数（如 <code>stories</code>），在命令行中是可选的。我们可以这么调用：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py --name=<span class="string">"Sherrerd Hall"</span></span><br></pre></td></tr></table></figure><ul><li>普通方法中定义的参数（如 <code>stairs_per_story</code>）在命令行中即可以是位置参数，也可以是选项参数。我们可以这么调用：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 作为位置参数</span></span><br><span class="line">$ python example.py --name=<span class="string">"Sherrerd Hall"</span> climb_stairs 10</span><br><span class="line"><span class="comment"># 作为选项参数</span></span><br><span class="line">$ python example.py --name=<span class="string">"Sherrerd Hall"</span> climb_stairs --stairs_per_story=10</span><br></pre></td></tr></table></figure><ul><li>选项参数中的横杠（<code>-</code>）和下划线（<code>_</code>）是等价的。因此也可以这么调用：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 作为选项参数</span></span><br><span class="line">$ python example.py --name=<span class="string">"Sherrerd Hall"</span> climb_stairs --stairs-per-story=10</span><br></pre></td></tr></table></figure><p>此外，<code>fire</code> 还支持在函数中定义 <code>*args</code> 和 <code>**kwargs</code>。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">fargs</span><span class="params">(*args)</span>:</span></span><br><span class="line">  <span class="keyword">return</span> str(args)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">fkwargs</span><span class="params">(**kwargs)</span>:</span></span><br><span class="line">  <span class="keyword">return</span> str(kwargs)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire()</span><br></pre></td></tr></table></figure><ul><li>函数中的 <code>*args</code> 在命令行中为位置参数。我们可以这么调用：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py fargs a b c</span><br></pre></td></tr></table></figure><ul><li>函数中的 <code>**kwargs</code> 在命令行中为选项参数。我们可以这么调用：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py fargs --a a1 --b b1 --c c1</span><br></pre></td></tr></table></figure><ul><li>通过分隔符 <code>-</code> 可显式告知分隔符后的为子命令，而非命令的参数。且看下面的示例：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 没有使用分隔符，upper 被作为位置参数</span></span><br><span class="line">$ python example.py fargs a b c upper</span><br><span class="line">(<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'upper'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用了分隔符，upper 被作为子命令</span></span><br><span class="line">$ python example.py fargs a b c - upper</span><br><span class="line">(<span class="string">'A'</span>, <span class="string">'B'</span>, <span class="string">'C'</span>)</span><br></pre></td></tr></table></figure><ul><li>通过 <code>fire</code> 内置的 <code>--separator</code> 可以自定义分隔符，此选项参数需要跟在单独的 <code>--</code> 后面：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py a b c X upper -- --separator=X</span><br><span class="line">(<span class="string">'A'</span>, <span class="string">'B'</span>, <span class="string">'C'</span>)</span><br></pre></td></tr></table></figure><h3 id="2-4-参数类型"><a href="#2-4-参数类型" class="headerlink" title="2.4 参数类型"></a>2.4 参数类型</h3><p>在 <code>fire</code> 中，参数的类型由其值决定，通过下面的简单代码，我们可以看到给不同的值时，<code>fire</code>会解析为什么类型：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line">fire.Fire(<span class="keyword">lambda</span> obj: type(obj).__name__)</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py 10</span><br><span class="line">int</span><br><span class="line">$ python example.py 10.0</span><br><span class="line"><span class="built_in">float</span></span><br><span class="line">$ python example.py hello</span><br><span class="line">str</span><br><span class="line">$ python example.py <span class="string">'(1,2)'</span></span><br><span class="line">tuple</span><br><span class="line">$ python example.py [1,2]</span><br><span class="line">list</span><br><span class="line">$ python example.py True</span><br><span class="line">bool</span><br><span class="line">$ python example.py &#123;name: David&#125;</span><br><span class="line">dict</span><br></pre></td></tr></table></figure><p>如果想传递字符串形式的数字，那就需要小心引号了，要么把引号引起来，要么转义引号：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 数字 10</span></span><br><span class="line">$ python example.py 10</span><br><span class="line">int</span><br><span class="line"><span class="comment"># 没有对引号处理，仍然是数字10</span></span><br><span class="line">$ python example.py <span class="string">"10"</span></span><br><span class="line">int</span><br><span class="line"><span class="comment"># 把引号引起来，所以是字符串“10”</span></span><br><span class="line">$ python example.py <span class="string">'"10"'</span></span><br><span class="line">str</span><br><span class="line"><span class="comment"># 另一种把引号引起来的形式</span></span><br><span class="line">$ python example.py <span class="string">"'10'"</span></span><br><span class="line">str</span><br><span class="line"><span class="comment"># 转义引号</span></span><br><span class="line">$ python example.py \<span class="string">"10\"</span></span><br><span class="line"><span class="string">str</span></span><br></pre></td></tr></table></figure><p>考虑下更复杂的场景，如果传递的是字典，在字典中有字符串，那么也是要小心引号的：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 推荐做法</span></span><br><span class="line">$ python example.py <span class="string">'&#123;"name": "David Bieber"&#125;'</span></span><br><span class="line">dict</span><br><span class="line"><span class="comment"># 也是可以的</span></span><br><span class="line">$ python example.py &#123;<span class="string">"name"</span>:<span class="string">'"David Bieber"'</span>&#125;</span><br><span class="line">dict</span><br><span class="line"><span class="comment"># 错误，会被解析为字符串</span></span><br><span class="line">$ python example.py &#123;<span class="string">"name"</span>:<span class="string">"David Bieber"</span>&#125;</span><br><span class="line">str</span><br><span class="line"><span class="comment"># 错误，不会作为单个参数（因为中间有空格），报错</span></span><br><span class="line">$ python example.py &#123;<span class="string">"name"</span>: <span class="string">"David Bieber"</span>&#125;</span><br><span class="line">&lt;error&gt;</span><br></pre></td></tr></table></figure><p>如果值为 <code>True</code> 或 <code>False</code> 将为视为布尔值，<code>fire</code> 还支持通过 <code>--name</code> 将 <code>name</code> 设为 <code>True</code>，或通过 <code>--noname</code> 将 <code>name</code> 设为 <code>False</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py --obj=True</span><br><span class="line">bool</span><br><span class="line">$ python example.py --obj=False</span><br><span class="line">bool</span><br><span class="line">$ python example.py --obj</span><br><span class="line">bool</span><br><span class="line">$ python example.py --noobj</span><br><span class="line">bool</span><br></pre></td></tr></table></figure><h3 id="2-5-Fire-内置选项参数"><a href="#2-5-Fire-内置选项参数" class="headerlink" title="2.5 Fire 内置选项参数"></a>2.5 Fire 内置选项参数</h3><p>Fire 内置了一些选项参数，以帮助我们更容易地使用命令行程序。若想使用内置的选项功能，需要将选项参数跟在 <code>--</code> 后，在上文中，我们介绍了 <code>--separator</code> 参数，除了它，<code>fire</code> 还支持以下选项参数：</p><ul><li><code>command -- --help</code> 列出详细的帮助信息</li><li><code>command -- --interactive</code> 进入交互式模式</li><li><code>command -- --completion [shell]</code> 生成 CLI 程序的自动补全脚本，以支持自动补全</li><li><code>command -- --trace</code> 获取命令的 Fire 追踪以了解调用 Fire 后究竟发生了什么</li><li><code>command -- --verbose</code> 获取包含私有成员在内的详情</li></ul><h2 id="三、小结"><a href="#三、小结" class="headerlink" title="三、小结"></a>三、小结</h2><p><code>fire</code> 让命令行程序的实现变得特别简单，本文着重介绍了它的链式调用、选项参数、位置参数、参数类型以及内置选项参数。<code>fire</code> 的概念并不多，真正践行了“把简单留给他人，把复杂留给自己”的理念。</p><p><code>fire</code> 的介绍就告一段落，它绝对会是你编写命令行程序的一大利器。在下一篇文章中，我们依然会通过实现一个简单的 <code>git</code> 程序来进行 <code>fire</code> 的实战。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ul><li><a href="/2019/08/13/Python-命令行之旅：初探-argparse/" title="Python 命令行之旅：初探 argparse">Python 命令行之旅：初探 argparse</a></li><li><a href="/2019/08/20/Python-命令行之旅：深入-argparse（一）/" title="Python 命令行之旅：深入 argparse（一）">Python 命令行之旅：深入 argparse（一）</a></li><li><a href="/2019/08/27/Python-命令行之旅：深入-argparse（二）/" title="Python 命令行之旅：深入 argparse（二）">Python 命令行之旅：深入 argparse（二）</a></li><li><a href="/2019/09/04/Python-命令行之旅：使用-argparse-实现-git-命令/" title="Python 命令行之旅：使用 argparse 实现 git 命令">Python 命令行之旅：使用 argparse 实现 git 命令</a></li><li><a href="/2019/10/09/Python-命令行之旅：初探-docopt/" title="Python 命令行之旅：初探 docopt">Python 命令行之旅：初探 docopt</a></li><li><a href="/2019/10/15/Python-命令行之旅：深入-docopt/" title="Python 命令行之旅：深入 docopt">Python 命令行之旅：深入 docopt</a></li><li><a href="/2019/10/30/Python-命令行之旅：使用-docopt-实现-git-命令/" title="Python 命令行之旅：使用 docopt 实现 git 命令">Python 命令行之旅：使用 docopt 实现 git 命令</a></li><li><a href="/2019/11/04/Python-命令行之旅：初探-click/" title="Python 命令行之旅：初探 click">Python 命令行之旅：初探 click</a></li><li><a href="/2019/11/11/Python-命令行之旅：深入-click（一）/" title="Python 命令行之旅：深入 click（一）">Python 命令行之旅：深入 click（一）</a></li><li><a href="/2019/11/17/Python-命令行之旅：深入-click（二）/" title="Python 命令行之旅：深入 click（二）">Python 命令行之旅：深入 click（二）</a></li><li><a href="/2019/11/25/Python-命令行之旅：深入-click（三）/" title="Python 命令行之旅：深入 click（三）">Python 命令行之旅：深入 click（三）</a></li><li><a href="/2019/12/09/Python-命令行之旅：深入-click（四）/" title="Python 命令行之旅：深入 click（四）">Python 命令行之旅：深入 click（四）</a></li><li><a href="/2019/12/14/Python-命令行之旅：使用-click-实现-git-命令/" title="Python 命令行之旅：使用 click 实现 git 命令">Python 命令行之旅：使用 click 实现 git 命令</a></li><li><a href="/2019/12/22/Python-命令行之旅：初探-fire/" title="Python 命令行之旅：初探 fire">Python 命令行之旅：初探 fire</a></li><li><a href="/2019/12/29/Python-命令行之旅：深入-fire（一）/" title="Python 命令行之旅：深入 fire（一）">Python 命令行之旅：深入 fire（一）</a></li><li><a href="/2020/01/05/Python-命令行之旅：深入-fire（二）/" title="Python 命令行之旅：深入 fire（二）">Python 命令行之旅：深入 fire（二）</a></li><li><a href="/2020/01/12/Python-命令行之旅：使用-fire-实现-git-命令/" title="Python 命令行之旅：使用 fire 实现 git 命令">Python 命令行之旅：使用 fire 实现 git 命令</a></li><li><a href="/2020/02/09/Python-命令行之旅：argparse、docopt、click-和-fire-总结篇/" title="Python 命令行之旅：argparse、docopt、click 和 fire 总结篇">Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</a></li><li><a href="/2020/07/02/Python-命令行大乱斗/" title="Python 命令行大乱斗">Python 命令行大乱斗</a></li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;在上一篇文章中我们介绍了 &lt;code&gt;fire&lt;/code&gt; 的子命令、嵌套命令和属性访问等内容，今天我们将继续深入了解 &lt;code&gt;fire&lt;/code&gt; 的其他功能。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/categories/Python/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
      <category term="fire" scheme="http://prodesire.cn/tags/fire/"/>
    
  </entry>
  
  <entry>
    <title>Python 壹周刊 006</title>
    <link href="http://prodesire.cn/2019/12/31/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-006/"/>
    <id>http://prodesire.cn/2019/12/31/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-006/</id>
    <published>2019-12-31T12:59:24.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="新鲜事儿"><a href="#新鲜事儿" class="headerlink" title="新鲜事儿"></a>新鲜事儿</h2><p>本周没有新鲜事儿~</p><h2 id="好文共赏"><a href="#好文共赏" class="headerlink" title="好文共赏"></a>好文共赏</h2><p><a href="https://florimond.dev/blog/articles/2019/08/introduction-to-asgi-async-python-web/" target="_blank" rel="noopener">ASGI 简介：异步 Python Web 生态系统的出现</a></p><p>如果您认为 Python 已陷入数据科学领域，请再考虑一遍！有了 async 特性，Python Web 开发又回来了，这很令人兴奋。</p><a id="more"></a><p><a href="https://tryexceptpass.org/article/distributing-python-applications/" target="_blank" rel="noopener">2020 年发布 Python 应用程序的 12 种趋势替代方案</a></p><p>2019 年 Python 生态中最流行的主题之一是打包和分发。 随着一年的结束，我想总结一下我们目前可用于分发 Python 应用程序的方式，其中一些也适用于任何语言。</p><p><a href="https://realpython.com/numpy-scipy-pandas-correlation-python/" target="_blank" rel="noopener">通过 NumPy、SciPy 和 Pandas 玩转相关性</a></p><p>了解什么是相关性以及如何使用 Python 计算相关性。本文将介绍使用 SciPy、NumPy 和 Pandas 相关方法来计算三个不同的相关系数，还会介绍如何使用 Matplotlib 可视化数据、回归线和相关矩阵。</p><p><a href="https://gawron.sdsu.edu/compling/course_core/python_intro/intro_lecture_files/fastpython.html" target="_blank" rel="noopener">Python 性能技巧</a></p><p>各类提升 Python 程序性能的技巧。</p><p><a href="https://blog.miguelgrinberg.com/post/how-to-make-python-wait" target="_blank" rel="noopener">如何让 Python 等待</a></p><p>对于许多类型的应用程序，有时需要暂停程序的运行，直到发生某些外部情况为止。 我们需要找出一种让脚本等待的方法，这要想正确地做起来并不像听起来那样简单！在本文中，我将向您展示几种不同的等待方式。 我将在所有示例中使用 Python，这个概念也适用于所有编程语言。</p><p><a href="https://orbifold.xyz/pyhamcrest.html" target="_blank" rel="noopener">使用 PyHamcrest 进行精确的单元测试</a></p><p>Hamcrest 是一个 Python 框架，旨在使测试断言更易于编写和更加精确。</p><p><a href="https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html" target="_blank" rel="noopener">Python import 语句权威指南</a></p><p>如何在 Python 2 和 3 中解决常见的导入问题。</p><p><a href="https://sausheong.github.io/posts/pi4-dev-ipadpro/" target="_blank" rel="noopener">将 Raspberry Pi 4 设置为 iPad Pro 的开发机器</a></p><p><a href="https://www.pluralsight.com/tech-blog/porting-flask-to-fastapi-for-ml-model-serving/" target="_blank" rel="noopener">为了 ML 模型服务，将 Flask 移植到 FastAPI</a></p><p>Flask 因其简单性而是一个非常流行的 Web 框架，用于在 Python 中构建 REST API，尤其是服务于机器学习模型。 在本文中，我们将学习如何迁移到更新的 FastAPI 框架，以利用类型检查和异步编程的优势。</p><p><a href="https://hatem-hassan.com/blog/fullstack-nlp-building-and-deploying-end-to-end-fake-news-classifier" target="_blank" rel="noopener">全栈 NLP：构建和部署端到端虚假新闻分类器</a></p><p>这是一个有关构建 NLP 文本分类 Web 应用程序并将其部署到生产环境的教程。</p><p><a href="https://towardsdatascience.com/the-video-search-engine-my-journey-into-computer-vision-9789824e76bb" target="_blank" rel="noopener">视频搜索引擎—我的计算机视觉之旅</a></p><p>制作视频很容易，但是谁会有时间观看所有视频？我想出一个视频搜索引擎，用来查找想要的瞬间，并提供了原型。</p><p><a href="https://dev.to/billm/how-does-a-simple-web-server-work-2mb5" target="_blank" rel="noopener">一个简单的 Web 服务是如何工作的</a></p><p><a href="https://dev.to/codemouse92/dead-simple-python-working-with-files-lmg" target="_blank" rel="noopener">非常简单的 Python：处理文件</a></p><p>关于文件处理，基本用法和坑都在这里。</p><h2 id="赞视频"><a href="#赞视频" class="headerlink" title="赞视频"></a>赞视频</h2><p><a href="https://realpython.com/courses/python-dictionary-iteration/" target="_blank" rel="noopener">Python 字典迭代：高级技巧</a></p><p>字典是 Python 中最重要和有用的数据结构，能帮助解决各类问题。本课程将带你深入了解如何迭代字典。</p><p><a href="https://www.youtube.com/watch?v=lbbNoCFSBV4" target="_blank" rel="noopener">使用 Python 在终端中绘制动画圣诞树</a></p><p>做了一个圣诞节特别的编程项目 —— 制作在终端上运行的动画圣诞树。 有时编程可能很有趣，就比如这个。</p><h2 id="酷开源"><a href="#酷开源" class="headerlink" title="酷开源"></a>酷开源</h2><p><a href="https://github.com/microsoft/nlp-recipes" target="_blank" rel="noopener">nlp-recipes</a></p><p>自然语言处理的最佳实践和示例。</p><p><a href="https://github.com/mardix/assembly" target="_blank" rel="noopener">assembly</a></p><p>基于 Flask 的 Pythonic OOP Web 框架。</p><p><a href="https://github.com/onelivesleft/PrettyErrors/" target="_blank" rel="noopener">PrettyErrors</a></p><p>让 Python 的异常输出变得更优雅。</p><p><a href="https://github.com/seandstewart/typical" target="_blank" rel="noopener">typical</a></p><p>快速、简单、正确的利用 Python 3 类型注解的数据校验库。</p><p><a href="https://github.com/wkentaro/labelme" target="_blank" rel="noopener">labelme</a></p><p>Python 的图像多边形注释（多边形、矩形、圆形、直线、点和图像级标记注释）。</p><p><a href="https://github.com/bridgecrewio/checkov" target="_blank" rel="noopener">Checkov</a></p><p>Checkov 是用于基础结构即代码的静态代码分析工具。它扫描使用 Terraform 设置的云基础架构，检测安全性和合规性错误配置。</p><p><a href="https://github.com/skamieniarz/newspie" target="_blank" rel="noopener">NewsPie</a></p><p>一个由 Flask 构建并由 News API 支持的简约新闻聚合器。</p><p><a href="https://github.com/joelibaceta/pix-to-xls" target="_blank" rel="noopener">pix-to-xls</a></p><p>一个可以将图像转换为 excel 彩色单元格的简单工具。</p><p><a href="https://github.com/s0md3v/Silver" target="_blank" rel="noopener">Silver</a></p><p>masscan 速度很快、nmap 可以提取软件指纹、而 vulners 是一个巨大的漏洞数据库。Silver 是一个允许完整使用这些程序能力的前端，它可以通过解析数据、生成并行进程、缓存漏洞数据来进行更快的扫描，且不止于此。</p><p><a href="https://github.com/marshmallow-code/apispec" target="_blank" rel="noopener">apispec</a></p><p>可插拔的 API 规范生成器，目前支持 OpenAPI 规范（又称 Swagger 规范）。</p><p><a href="https://github.com/pvcraven/arcade" target="_blank" rel="noopener">arcade</a></p><p>易于使用的用于创建 2D 街机游戏的 Python 库。</p><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;新鲜事儿&quot;&gt;&lt;a href=&quot;#新鲜事儿&quot; class=&quot;headerlink&quot; title=&quot;新鲜事儿&quot;&gt;&lt;/a&gt;新鲜事儿&lt;/h2&gt;&lt;p&gt;本周没有新鲜事儿~&lt;/p&gt;
&lt;h2 id=&quot;好文共赏&quot;&gt;&lt;a href=&quot;#好文共赏&quot; class=&quot;headerlink&quot; title=&quot;好文共赏&quot;&gt;&lt;/a&gt;好文共赏&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://florimond.dev/blog/articles/2019/08/introduction-to-asgi-async-python-web/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ASGI 简介：异步 Python Web 生态系统的出现&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果您认为 Python 已陷入数据科学领域，请再考虑一遍！有了 async 特性，Python Web 开发又回来了，这很令人兴奋。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/categories/Python/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/tags/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
  </entry>
  
  <entry>
    <title>Python 命令行之旅：深入 fire（一）</title>
    <link href="http://prodesire.cn/2019/12/29/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E6%B7%B1%E5%85%A5-fire%EF%BC%88%E4%B8%80%EF%BC%89/"/>
    <id>http://prodesire.cn/2019/12/29/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E6%B7%B1%E5%85%A5-fire%EF%BC%88%E4%B8%80%EF%BC%89/</id>
    <published>2019-12-29T14:05:17.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>在第一篇“初探 fire”的文章中，我们初步掌握了使用 <code>fire</code> 的简单步骤，了解了它 Pythonic 的用法。</p><p>今天我们将深入了解 <code>fire</code> 的子命令、嵌套命令和属性访问功能。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">本系列文章默认使用 Python 3 作为解释器进行讲解。</span><br><span class="line">若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~</span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="二、功能"><a href="#二、功能" class="headerlink" title="二、功能"></a>二、功能</h2><h3 id="2-1-子命令"><a href="#2-1-子命令" class="headerlink" title="2.1 子命令"></a>2.1 子命令</h3><p>使用 <code>fire</code> 实现子命令有多种方式：</p><h4 id="2-1-1-定义若干函数，使用-fire-Fire"><a href="#2-1-1-定义若干函数，使用-fire-Fire" class="headerlink" title="2.1.1 定义若干函数，使用 fire.Fire()"></a>2.1.1 定义若干函数，使用 fire.Fire()</h4><p>实现子命令最简单的方式就是定义若干个函数，每个函数名隐式就是子命令名称，然后调用 <code>fire.Fire()</code> 变将当前模块所有的函数解析为对应的子命令的处理函数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(x, y)</span>:</span></span><br><span class="line">  <span class="keyword">return</span> x + y</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">multiply</span><span class="params">(x, y)</span>:</span></span><br><span class="line">  <span class="keyword">return</span> x * y</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire()</span><br></pre></td></tr></table></figure><p>然后我们就可以在命令行中这么调用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py add 10 20</span><br><span class="line">30</span><br><span class="line">$ python example.py multiply 10 20</span><br><span class="line">200</span><br></pre></td></tr></table></figure><p>关于如何识别参数类型，比如上述 <code>add 10 20</code> 中 <code>10</code> 和 <code>20</code> 是作为数字而非字符串，我们会在下篇文章的参数解析章节中进行讲解。</p><h4 id="2-1-2-定义若干函数，使用-fire-Fire"><a href="#2-1-2-定义若干函数，使用-fire-Fire" class="headerlink" title="2.1.2 定义若干函数，使用 fire.Fire()"></a>2.1.2 定义若干函数，使用 fire.Fire(<dict>)</dict></h4><p>在 <code>2.1.1</code> 的版本中，会把所有函数都当做是子命令。有时我们可能只想把部分函数当做子命令，或者是希望子命令名称和函数名称不一样。这个时候我们就可以通过字典对象显式地告诉 <code>fire</code>。</p><p>字典对象的形式为 <code>{&#39;子命令名称&#39;: 函数}</code>，比如前面的示例中，我们希望最终的子命令为 <code>add</code> 和 <code>mul</code>，那么就可以这么写：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">fire.Fire(&#123;</span><br><span class="line">  <span class="string">'add'</span>: add,</span><br><span class="line">  <span class="string">'mul'</span>: multiply,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>然后我们就可以在命令行中这么调用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py add 10 20</span><br><span class="line">30</span><br><span class="line">$ python example.py mul 10 20</span><br><span class="line">200</span><br></pre></td></tr></table></figure><h4 id="2-1-3-定义类和方法，使用-fire-Fire"><a href="#2-1-3-定义类和方法，使用-fire-Fire" class="headerlink" title="2.1.3 定义类和方法，使用 fire.Fire()"></a>2.1.3 定义类和方法，使用 fire.Fire(<object>)</object></h4><p>定义类和方法的这种方式我们在上一篇文章中介绍过，它和定义函数的方式基本相同，只不过是用类的方式来组织。</p><p>然后将类实例化，并把实例化的对象多为 <code>fire.Fire</code> 的入参：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Calculator</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(self, x, y)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> x + y</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">multiply</span><span class="params">(self, x, y)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> x * y</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  calculator = Calculator()</span><br><span class="line">  fire.Fire(calculator)</span><br></pre></td></tr></table></figure><h4 id="2-1-4-定义类和方法，使用-fire-Fire"><a href="#2-1-4-定义类和方法，使用-fire-Fire" class="headerlink" title="2.1.4 定义类和方法，使用 fire.Fire()"></a>2.1.4 定义类和方法，使用 fire.Fire(<class>)</class></h4><p>和 <code>2.1.3</code> 中的唯一不同点是把类而非实例对象作为 <code>fire.Fire</code> 的入参：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fire.Fire(Calculator)</span><br></pre></td></tr></table></figure><p>传递类和实例对象的基本作用是一样的，但传递类还有一个额外的特性：如果构造函数中定义了参数，那么这些参数都会作为整个命令行程序的选项参数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BrokenCalculator</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, offset=<span class="number">1</span>)</span>:</span></span><br><span class="line">      self._offset = offset</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(self, x, y)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> x + y + self._offset</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">multiply</span><span class="params">(self, x, y)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> x * y + self._offset</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire(BrokenCalculator)</span><br></pre></td></tr></table></figure><p>查看帮助命令有：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py --<span class="built_in">help</span></span><br><span class="line">INFO: Showing <span class="built_in">help</span> with the <span class="built_in">command</span> <span class="string">'example.py -- --help'</span>.</span><br><span class="line"></span><br><span class="line">NAME</span><br><span class="line">    example.py</span><br><span class="line"></span><br><span class="line">SYNOPSIS</span><br><span class="line">    example.py &lt;flags&gt;</span><br><span class="line"></span><br><span class="line">FLAGS</span><br><span class="line">    --offset=OFFSET</span><br></pre></td></tr></table></figure><p>由此可见构造函数 <code>BrokenCalculator.__init__(self, offset=1)</code> 中的 <code>offset</code> 自动转换为了命令行中的全局选项参数 <code>--offset</code>，且默认值为 <code>1</code>。</p><p>我们可以在命令行中这么调用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py add 10 20</span><br><span class="line">31</span><br><span class="line">$ python example.py multiply 10 20</span><br><span class="line">201</span><br><span class="line">$ python example.py add 10 20 --offset=0</span><br><span class="line">30</span><br><span class="line">$ python example.py multiply 10 20 --offset=0</span><br><span class="line">200</span><br></pre></td></tr></table></figure><h3 id="2-2-命令组-嵌套命令"><a href="#2-2-命令组-嵌套命令" class="headerlink" title="2.2 命令组/嵌套命令"></a>2.2 命令组/嵌套命令</h3><p>想要实现嵌套命令，可将多个类组织起来，示例如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">IngestionStage</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">'Ingesting! Nom nom nom...'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DigestionStage</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self, volume=<span class="number">1</span>)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">' '</span>.join([<span class="string">'Burp!'</span>] * volume)</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">status</span><span class="params">(self)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">'Satiated.'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Pipeline</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">    self.ingestion = IngestionStage()</span><br><span class="line">    self.digestion = DigestionStage()</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self)</span>:</span></span><br><span class="line">    self.ingestion.run()</span><br><span class="line">    self.digestion.run()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire(Pipeline)</span><br></pre></td></tr></table></figure><p>在上面的示例中：</p><ul><li><code>IngestionStage</code> 实现了子命令 <code>run</code></li><li><code>DigestionStage</code> 实现了子命令 <code>run</code> 和 <code>status</code></li><li><code>Pipeline</code> 的构造函数中将 <code>IngestionStage</code> 实例化为 <code>ingestion</code>，将 <code>DigestionStage</code> 实例化为 <code>digestion</code>，就将这两个放到一个命令组中，因而支持了：<ul><li><code>ingestion run</code></li><li><code>digestion run</code></li><li><code>digestion status</code></li></ul></li><li><code>Pipeline</code> 实现了子命令 <code>run</code></li></ul><p>因此整个命令行程序支持如下命令：</p><ul><li><code>run</code></li><li><code>ingestion run</code></li><li><code>digestion run</code></li><li><code>digestion status</code></li></ul><p>然后我们就可以在命令行中这么调用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py run</span><br><span class="line">Ingesting! Nom nom nom...</span><br><span class="line">Burp!</span><br><span class="line">$ python example.py ingestion run</span><br><span class="line">Ingesting! Nom nom nom...</span><br><span class="line">$ python example.py digestion run</span><br><span class="line">Burp!</span><br><span class="line">$ python example.py digestion status</span><br><span class="line">Satiated.</span><br></pre></td></tr></table></figure><h3 id="2-3-属性访问"><a href="#2-3-属性访问" class="headerlink" title="2.3 属性访问"></a>2.3 属性访问</h3><p><code>属性访问</code> 是 <code>fire</code> 相对于其他命令行库来说一个比较独特的功能。所谓访问属性是获取预置的属性所对应的值。</p><p>举个例子，在命令行中指定 <code>--code</code> 来告知程序要查询的程序编码，然后希望通过 <code>zipcode</code> 属性返回邮编，通过 <code>city</code> 属性返回城市名。那么属性可实现为实例成员属性：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line">cities = &#123;</span><br><span class="line">  <span class="string">'hz'</span>: (<span class="number">310000</span>, <span class="string">'杭州'</span>),</span><br><span class="line">  <span class="string">'bj'</span>: (<span class="number">100000</span>, <span class="string">'北京'</span>),</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">City</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, code)</span>:</span></span><br><span class="line">    info = cities.get(code)</span><br><span class="line">    self.zipcode = info[<span class="number">0</span>] <span class="keyword">if</span> info <span class="keyword">else</span> <span class="keyword">None</span></span><br><span class="line">    self.city = info[<span class="number">1</span>] <span class="keyword">if</span> info <span class="keyword">else</span> <span class="keyword">None</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire(City)</span><br></pre></td></tr></table></figure><p>使用方式如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ python example.py --code bj zipcode</span><br><span class="line">100000</span><br><span class="line">$ python example.py --code hz city</span><br><span class="line">杭州</span><br></pre></td></tr></table></figure><h2 id="三、小结"><a href="#三、小结" class="headerlink" title="三、小结"></a>三、小结</h2><p>使用 <code>fire</code> 实现子命令和嵌套命令相对于其他命令行库来说都更加简单清晰，不仅如此，<code>fire</code> 还提供了属性访问这种较为独特的能力。</p><p>在下篇文章中，我们将进一步深入了解 <code>fire</code>，介绍其链式函数调用、自定义序列化、参数解析、fire 选项等更加高阶的功能。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ul><li><a href="/2019/08/13/Python-命令行之旅：初探-argparse/" title="Python 命令行之旅：初探 argparse">Python 命令行之旅：初探 argparse</a></li><li><a href="/2019/08/20/Python-命令行之旅：深入-argparse（一）/" title="Python 命令行之旅：深入 argparse（一）">Python 命令行之旅：深入 argparse（一）</a></li><li><a href="/2019/08/27/Python-命令行之旅：深入-argparse（二）/" title="Python 命令行之旅：深入 argparse（二）">Python 命令行之旅：深入 argparse（二）</a></li><li><a href="/2019/09/04/Python-命令行之旅：使用-argparse-实现-git-命令/" title="Python 命令行之旅：使用 argparse 实现 git 命令">Python 命令行之旅：使用 argparse 实现 git 命令</a></li><li><a href="/2019/10/09/Python-命令行之旅：初探-docopt/" title="Python 命令行之旅：初探 docopt">Python 命令行之旅：初探 docopt</a></li><li><a href="/2019/10/15/Python-命令行之旅：深入-docopt/" title="Python 命令行之旅：深入 docopt">Python 命令行之旅：深入 docopt</a></li><li><a href="/2019/10/30/Python-命令行之旅：使用-docopt-实现-git-命令/" title="Python 命令行之旅：使用 docopt 实现 git 命令">Python 命令行之旅：使用 docopt 实现 git 命令</a></li><li><a href="/2019/11/04/Python-命令行之旅：初探-click/" title="Python 命令行之旅：初探 click">Python 命令行之旅：初探 click</a></li><li><a href="/2019/11/11/Python-命令行之旅：深入-click（一）/" title="Python 命令行之旅：深入 click（一）">Python 命令行之旅：深入 click（一）</a></li><li><a href="/2019/11/17/Python-命令行之旅：深入-click（二）/" title="Python 命令行之旅：深入 click（二）">Python 命令行之旅：深入 click（二）</a></li><li><a href="/2019/11/25/Python-命令行之旅：深入-click（三）/" title="Python 命令行之旅：深入 click（三）">Python 命令行之旅：深入 click（三）</a></li><li><a href="/2019/12/09/Python-命令行之旅：深入-click（四）/" title="Python 命令行之旅：深入 click（四）">Python 命令行之旅：深入 click（四）</a></li><li><a href="/2019/12/14/Python-命令行之旅：使用-click-实现-git-命令/" title="Python 命令行之旅：使用 click 实现 git 命令">Python 命令行之旅：使用 click 实现 git 命令</a></li><li><a href="/2019/12/22/Python-命令行之旅：初探-fire/" title="Python 命令行之旅：初探 fire">Python 命令行之旅：初探 fire</a></li><li><a href="/2019/12/29/Python-命令行之旅：深入-fire（一）/" title="Python 命令行之旅：深入 fire（一）">Python 命令行之旅：深入 fire（一）</a></li><li><a href="/2020/01/05/Python-命令行之旅：深入-fire（二）/" title="Python 命令行之旅：深入 fire（二）">Python 命令行之旅：深入 fire（二）</a></li><li><a href="/2020/01/12/Python-命令行之旅：使用-fire-实现-git-命令/" title="Python 命令行之旅：使用 fire 实现 git 命令">Python 命令行之旅：使用 fire 实现 git 命令</a></li><li><a href="/2020/02/09/Python-命令行之旅：argparse、docopt、click-和-fire-总结篇/" title="Python 命令行之旅：argparse、docopt、click 和 fire 总结篇">Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</a></li><li><a href="/2020/07/02/Python-命令行大乱斗/" title="Python 命令行大乱斗">Python 命令行大乱斗</a></li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;在第一篇“初探 fire”的文章中，我们初步掌握了使用 &lt;code&gt;fire&lt;/code&gt; 的简单步骤，了解了它 Pythonic 的用法。&lt;/p&gt;
&lt;p&gt;今天我们将深入了解 &lt;code&gt;fire&lt;/code&gt; 的子命令、嵌套命令和属性访问功能。&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;本系列文章默认使用 Python 3 作为解释器进行讲解。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/categories/Python/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
      <category term="fire" scheme="http://prodesire.cn/tags/fire/"/>
    
  </entry>
  
  <entry>
    <title>Python 命令行之旅：初探 fire</title>
    <link href="http://prodesire.cn/2019/12/22/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E5%88%9D%E6%8E%A2-fire/"/>
    <id>http://prodesire.cn/2019/12/22/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E5%88%9D%E6%8E%A2-fire/</id>
    <published>2019-12-22T12:52:23.000Z</published>
    <updated>2020-07-24T15:00:04.356Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>在本系列前面所有文章中，我们分别介绍了 <code>argparse</code>、<code>docopt</code> 和 <code>click</code> 的主要功能和用法。它们各具特色，都能出色地完成命令行任务。<code>argparse</code> 是面向过程的，需要先设置解析器，再定义参数，再解析命令行，最后实现业务逻辑。<code>docopt</code> 先用声明式的语法定义出参数，再过程式地解析命令行和实现业务逻辑。<code>click</code> 则是用装饰器的方式进一步简化显式的命令调用逻辑，但仍然不够面向对象。</p><p>而今天要介绍的 <a href="https://github.com/google/python-fire" target="_blank" rel="noopener">fire</a> 则是用一种面向广义对象的方式来玩转命令行，这种对象可以是类、函数、字典、列表等，它更加灵活，也更加简单。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">本系列文章默认使用 Python 3 作为解释器进行讲解。</span><br><span class="line">若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~</span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="二、介绍"><a href="#二、介绍" class="headerlink" title="二、介绍"></a>二、介绍</h2><p><a href="https://github.com/google/python-fire" target="_blank" rel="noopener">fire</a> 可以根据任何 Python 对象自动生成命令行接口。它有如下特性：</p><ul><li>能以简单的方式生成 CLI</li><li>是一个开发和调试 Python 代码的实用工具</li><li>能将现存代码或别人的代码转换为 CLI</li><li>使得在 Bash 和 Python 间的转换变得更容易</li><li>通过预先为 REPL 设置所需的模块和变量，使得实用 REPL 更加容易</li></ul><p>通过如下命令可快速安装 <code>fire</code> 库：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install fire</span><br></pre></td></tr></table></figure><h2 id="三、快速开始"><a href="#三、快速开始" class="headerlink" title="三、快速开始"></a>三、快速开始</h2><p>回忆下使用 <code>argparse</code>、<code>docopt</code> 和 <code>click</code> 实现命令行程序的步骤：</p><ul><li>对于 <code>argparse</code> 来说，要先设置解析器，再定义参数，再解析命令行，最后实现业务逻辑</li><li>对于 <code>docopt</code> 来说，要先定义定义接口描述，再解析命令行，最后实现业务逻辑</li><li>对于 <code>click</code> 来说，就是实现业务逻辑和通过装饰器的方式定义参数</li></ul><p>它们的实现步骤越来越简单，从四步简化到了两步。而今天的主角 <code>fire</code> 则是跟进一步，只需实现业务逻辑就够了。</p><p>这简直简单的不可思议，为什么这样做就够了？我们不妨考虑下 Python 中的函数，函数是不是可以对应一个命令行程序，而函数的参数可以对应命令行程序的参数和选项呢？再看看 Python 中的类，一个类是不是可以对应一个命令行程序，而类中的每个实例方法就可以对应子命令，实例方法中的参数就是对应子命令的参数和选项。</p><p>这么一想，理论上确实是可以实现的，我们不妨通过下面的示例来看看 <code>fire</code> 是如何让我们通过简单的方式实现命令行程序。</p><h3 id="3-1-使用函数"><a href="#3-1-使用函数" class="headerlink" title="3.1 使用函数"></a>3.1 使用函数</h3><p>来看这么一个例子：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">hello</span><span class="params">(name=<span class="string">"World"</span>)</span>:</span></span><br><span class="line">  <span class="keyword">return</span> <span class="string">'Hello &#123;name&#125;!'</span>.format(name=name)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire(hello)</span><br></pre></td></tr></table></figure><p>在上述例子中定义一个 <code>hello</code> 函数，它接受 <code>name</code> 参数，并且有默认值 “World”。使用 <code>fire.Fire(hello)</code> 即可非常简单快速地实现命令功能，这个命令行就接受 <code>--name</code> 选项，不提供时使用默认值 “World”，提供时就按提供的值来。</p><p>可在命令行中执行下列命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">$ python hello.py</span><br><span class="line">Hello World!</span><br><span class="line">$ python hello.py --name=Prodesire</span><br><span class="line">Hello Prodesire!</span><br><span class="line">$ python hello.py --<span class="built_in">help</span></span><br><span class="line">INFO: Showing <span class="built_in">help</span> with the <span class="built_in">command</span> <span class="string">'hello.py -- --help'</span>.</span><br><span class="line"></span><br><span class="line">NAME</span><br><span class="line">    hello.py</span><br><span class="line"></span><br><span class="line">SYNOPSIS</span><br><span class="line">    hello.py &lt;flags&gt;</span><br><span class="line"></span><br><span class="line">FLAGS</span><br><span class="line">    --name=NAME</span><br></pre></td></tr></table></figure><h3 id="3-2-使用类"><a href="#3-2-使用类" class="headerlink" title="3.2 使用类"></a>3.2 使用类</h3><p>使用函数是最简单的方式，如果我们想以更有组织的方式来实现，比如使用类，<code>fire</code> 也是支持的。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fire</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Calculator</span><span class="params">(object)</span>:</span></span><br><span class="line">  <span class="string">"""A simple calculator class."""</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">double</span><span class="params">(self, number)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">2</span> * number</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">triple</span><span class="params">(self, number)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">3</span> * number</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  fire.Fire(Calculator)</span><br></pre></td></tr></table></figure><p>在上述例子中定义一个 <code>Calculator</code> 类，它有两个实例方法 <code>double</code> 和 <code>triple</code>，并且都接受 <code>number</code> 参数，没有默认值。使用 <code>fire.Fire(Calculator)</code> 即可非常简单快速地实现命令功能，这个命令行支持两个子命令 <code>double</code> 和 <code>triple</code>，位置参数 <code>NUMBER</code> 或选项参数 <code>--number</code></p><p>可在命令行中执行下列命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">$ python calculator.py double 10</span><br><span class="line">20</span><br><span class="line">$ python calculator.py triple --number=15</span><br><span class="line">45</span><br><span class="line">$ python calculator.py double --<span class="built_in">help</span></span><br><span class="line">INFO: Showing <span class="built_in">help</span> with the <span class="built_in">command</span> <span class="string">'calculator.py double -- --help'</span>.</span><br><span class="line"></span><br><span class="line">NAME</span><br><span class="line">    calculator.py double</span><br><span class="line"></span><br><span class="line">SYNOPSIS</span><br><span class="line">    calculator.py double NUMBER</span><br><span class="line"></span><br><span class="line">POSITIONAL ARGUMENTS</span><br><span class="line">    NUMBER</span><br><span class="line"></span><br><span class="line">NOTES</span><br><span class="line">    You can also use flags syntax <span class="keyword">for</span> POSITIONAL ARGUMENTS</span><br></pre></td></tr></table></figure><h2 id="四、小结"><a href="#四、小结" class="headerlink" title="四、小结"></a>四、小结</h2><p><code>fire</code> 的使用方式非常简单，定一个 Python 对象，剩下的就交给 <code>fire</code> 来处理，可谓是非常的 Pythonic，这也是它会如此受欢迎的原因。</p><p>除了上面展示的内容，<code>fire</code> 还支持更多种类的 Python 对象，也拥有很多强大的功能，我们将在接下来几节中逐步走近它。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ul><li><a href="/2019/08/13/Python-命令行之旅：初探-argparse/" title="Python 命令行之旅：初探 argparse">Python 命令行之旅：初探 argparse</a></li><li><a href="/2019/08/20/Python-命令行之旅：深入-argparse（一）/" title="Python 命令行之旅：深入 argparse（一）">Python 命令行之旅：深入 argparse（一）</a></li><li><a href="/2019/08/27/Python-命令行之旅：深入-argparse（二）/" title="Python 命令行之旅：深入 argparse（二）">Python 命令行之旅：深入 argparse（二）</a></li><li><a href="/2019/09/04/Python-命令行之旅：使用-argparse-实现-git-命令/" title="Python 命令行之旅：使用 argparse 实现 git 命令">Python 命令行之旅：使用 argparse 实现 git 命令</a></li><li><a href="/2019/10/09/Python-命令行之旅：初探-docopt/" title="Python 命令行之旅：初探 docopt">Python 命令行之旅：初探 docopt</a></li><li><a href="/2019/10/15/Python-命令行之旅：深入-docopt/" title="Python 命令行之旅：深入 docopt">Python 命令行之旅：深入 docopt</a></li><li><a href="/2019/10/30/Python-命令行之旅：使用-docopt-实现-git-命令/" title="Python 命令行之旅：使用 docopt 实现 git 命令">Python 命令行之旅：使用 docopt 实现 git 命令</a></li><li><a href="/2019/11/04/Python-命令行之旅：初探-click/" title="Python 命令行之旅：初探 click">Python 命令行之旅：初探 click</a></li><li><a href="/2019/11/11/Python-命令行之旅：深入-click（一）/" title="Python 命令行之旅：深入 click（一）">Python 命令行之旅：深入 click（一）</a></li><li><a href="/2019/11/17/Python-命令行之旅：深入-click（二）/" title="Python 命令行之旅：深入 click（二）">Python 命令行之旅：深入 click（二）</a></li><li><a href="/2019/11/25/Python-命令行之旅：深入-click（三）/" title="Python 命令行之旅：深入 click（三）">Python 命令行之旅：深入 click（三）</a></li><li><a href="/2019/12/09/Python-命令行之旅：深入-click（四）/" title="Python 命令行之旅：深入 click（四）">Python 命令行之旅：深入 click（四）</a></li><li><a href="/2019/12/14/Python-命令行之旅：使用-click-实现-git-命令/" title="Python 命令行之旅：使用 click 实现 git 命令">Python 命令行之旅：使用 click 实现 git 命令</a></li><li><a href="/2019/12/22/Python-命令行之旅：初探-fire/" title="Python 命令行之旅：初探 fire">Python 命令行之旅：初探 fire</a></li><li><a href="/2019/12/29/Python-命令行之旅：深入-fire（一）/" title="Python 命令行之旅：深入 fire（一）">Python 命令行之旅：深入 fire（一）</a></li><li><a href="/2020/01/05/Python-命令行之旅：深入-fire（二）/" title="Python 命令行之旅：深入 fire（二）">Python 命令行之旅：深入 fire（二）</a></li><li><a href="/2020/01/12/Python-命令行之旅：使用-fire-实现-git-命令/" title="Python 命令行之旅：使用 fire 实现 git 命令">Python 命令行之旅：使用 fire 实现 git 命令</a></li><li><a href="/2020/02/09/Python-命令行之旅：argparse、docopt、click-和-fire-总结篇/" title="Python 命令行之旅：argparse、docopt、click 和 fire 总结篇">Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</a></li><li><a href="/2020/07/02/Python-命令行大乱斗/" title="Python 命令行大乱斗">Python 命令行大乱斗</a></li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;在本系列前面所有文章中，我们分别介绍了 &lt;code&gt;argparse&lt;/code&gt;、&lt;code&gt;docopt&lt;/code&gt; 和 &lt;code&gt;click&lt;/code&gt; 的主要功能和用法。它们各具特色，都能出色地完成命令行任务。&lt;code&gt;argparse&lt;/code&gt; 是面向过程的，需要先设置解析器，再定义参数，再解析命令行，最后实现业务逻辑。&lt;code&gt;docopt&lt;/code&gt; 先用声明式的语法定义出参数，再过程式地解析命令行和实现业务逻辑。&lt;code&gt;click&lt;/code&gt; 则是用装饰器的方式进一步简化显式的命令调用逻辑，但仍然不够面向对象。&lt;/p&gt;
&lt;p&gt;而今天要介绍的 &lt;a href=&quot;https://github.com/google/python-fire&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;fire&lt;/a&gt; 则是用一种面向广义对象的方式来玩转命令行，这种对象可以是类、函数、字典、列表等，它更加灵活，也更加简单。&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;本系列文章默认使用 Python 3 作为解释器进行讲解。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/categories/Python/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
      <category term="fire" scheme="http://prodesire.cn/tags/fire/"/>
    
  </entry>
  
  <entry>
    <title>Python 壹周刊 005</title>
    <link href="http://prodesire.cn/2019/12/18/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-005/"/>
    <id>http://prodesire.cn/2019/12/18/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-005/</id>
    <published>2019-12-18T04:30:21.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="新鲜事儿"><a href="#新鲜事儿" class="headerlink" title="新鲜事儿"></a>新鲜事儿</h2><p><a href="https://pytorch.org/blog/openmined-and-pytorch-launch-fellowship-funding-for-privacy-preserving-ml/" target="_blank" rel="noopener">OpenMined 和 PyTorch 合作启动为保护隐私的 ML 社区提供研究金</a></p><p>PyTorch 团队已投资 25 万美元来支持 OpenMined 进一步发展和扩大隐私保护的 ML。你可以通过三种不同的机会参与该项目的开发。这些奖学金的每一项都进一步促进了我们的共同使命，即降低隐私保护机器学习的进入障碍，并创建一个更具隐私保护的世界。</p><a id="more"></a><h2 id="好文共赏"><a href="#好文共赏" class="headerlink" title="好文共赏"></a>好文共赏</h2><p><a href="https://dev.to/iammowgoud/fullstack-nlp-building-deploying-end-to-end-fake-news-classifier-56fb" target="_blank" rel="noopener">全栈 NLP：构建和部署端到端的虚假新闻分类器</a></p><p>这是一个构建 NLP 文本分类 Web 应用程序的 API + UI，并将其部署到生产环境的教程。</p><p><a href="https://martinheinz.dev/blog/1" target="_blank" rel="noopener">你从来没见过的 Python 技巧</a></p><p>有很多文章讨论 Python 的许多酷炫功能，例如变量解包、partial 函数、枚举可迭代对象，但 Python 还有很多要讨论的话题，因此在这里我将尝试展示一些我知道和使用但没在其他地方提过的功能。</p><p><a href="https://eng.uber.com/pplm/" target="_blank" rel="noopener">使用即插即用语言模型控制文本生成</a></p><p>Uber AI 的即插即用语言模型为 NLP 从业人员提供了将简单属性模型插入大型无条件语言模型的灵活性。</p><p><a href="https://nanonets.com/blog/ocr-with-tesseract/" target="_blank" rel="noopener">使用 Tesseract、OpenCV 和 Python 进行 OCR 的完整指南</a></p><p>有关在 Python 中使用 Tesseract 和 OpenCV 进行 OCR 的入门综合教程：包含预处理、深度学习 OCR、文本提取和限制。</p><p><a href="https://www.agiliq.com/blog/2019/11/writing-an-orm-for-redis/" target="_blank" rel="noopener">为 Redis 写一个 Python ORM</a></p><p>将实例存到 Redis 的一种优雅的方法。</p><p><a href="https://whalesalad.com/blog/doing-python-configuration-right" target="_blank" rel="noopener">正确地进行 Python 配置</a></p><p>让我们来讨论下如何配置 Python 应用程序，尤其是可能存在于多种环境中的各类情形——日常开发、预发、生产等。</p><h2 id="赞视频"><a href="#赞视频" class="headerlink" title="赞视频"></a>赞视频</h2><p><a href="https://www.youtube.com/watch?v=6ManltU_8iU&amp;feature=youtu.be" target="_blank" rel="noopener">初学者的 Django 3.0 完整教程</a></p><p><a href="https://www.youtube.com/watch?v=ek8GF76p5-s&amp;feature=youtu.be" target="_blank" rel="noopener">使用 Python 和 Pandas 处理 Excel 文件</a></p><p><a href="https://www.youtube.com/watch?v=dQxQsZ39XMU&amp;feature=youtu.be" target="_blank" rel="noopener">Python Lambda 表达式教程: what, how, when and why</a></p><p><a href="https://www.youtube.com/playlist?list=PLaeNpBNgqQWvxnFU4PYGLOJ82IvuePAyT" target="_blank" rel="noopener">North Bay Python 2019</a></p><h2 id="酷开源"><a href="#酷开源" class="headerlink" title="酷开源"></a>酷开源</h2><p><a href="https://wonderworks-software.github.io/PyFlow/" target="_blank" rel="noopener">PyFlow</a></p><p>Python 的可视化脚本框架</p><p><a href="https://github.com/getpelican/pelican" target="_blank" rel="noopener">Pelican</a></p><p>使用 Python 开发的，支持 Markdown 和 reST 的静态站点生成器。</p><p><a href="https://github.com/beetbox/beets" target="_blank" rel="noopener">beets</a></p><p>为音乐极客而生的媒体库管理系统。</p><p><a href="https://github.com/AtsushiSakai/PythonRobotics" target="_blank" rel="noopener">PythonRobotics</a></p><p>机器人算法的 Python 示例。</p><p><a href="https://github.com/ray-project/ray" target="_blank" rel="noopener">Ray</a></p><p>一个快速简单的用于构建和运行分布式应用程序的框架。</p><p><a href="https://github.com/web2py/pydal" target="_blank" rel="noopener">pyDAL</a></p><p>纯 Python 实现的数据库抽象层。</p><p><a href="https://github.com/kayak/pypika" target="_blank" rel="noopener">PyPika</a></p><p>一个 python SQL 查询构建器， 擅长各种 SQL 查询，尤其对数据分析特别有用。</p><p><a href="https://github.com/wemake-services/wemake-python-styleguide" target="_blank" rel="noopener">wemake-python-styleguide</a></p><p>有史以来最严格、最固执的 Python 检查器！</p><p><a href="https://github.com/AIDungeon/AIDungeon" target="_blank" rel="noopener">AI Dungeon 2</a></p><p>AI Dungeon 2 是完全由 AI 生成的文字冒险游戏，它使用 OpenAI 的最大 GPT-2 模型构建，是同类游戏中的第一个。它允许你进入游戏并对你所能想象的任何举动做出反应。</p><p><a href="https://github.com/python-mario/mario" target="_blank" rel="noopener">Mario: Shell pipes in Python</a></p><p>您是否想过直接在 Unix Shell 中使用 Python 函数？Mario 可以读写 csv、json 和 yaml；遍历树、甚至能执行 xpath 查询。另外，它还支持开箱即用的异步命令。你可以使用用简单的配置文件来构建自己的命令，并安装插件。</p><p><a href="https://github.com/M4cs/pixcryption" target="_blank" rel="noopener">Pixcryption</a></p><p>Pixcryption 的目标是通过图像提供一种新形式的信息隐匿/加密方式。它使用随机种子的 UUID 生成一个 user_key，该 user_key 与 RGB 完美值匹配以与 unicode 字符匹配。这些文件存储在 user_key.png 文件中，该文件用于加密和解密消息。</p><p><a href="https://github.com/thebjorn/pydeps" target="_blank" rel="noopener">pydeps</a></p><p>Python 模块依赖可视化。</p><p><a href="https://github.com/zh-plus/video-to-pose3D" target="_blank" rel="noopener">Video to Pose3D</a></p><p>一键将视频中人体运动转换为 3D 姿势。</p><p><a href="https://github.com/mjmikulski/horology" target="_blank" rel="noopener">Horology</a></p><p>用于 for 循环、上下文和函数的便捷时间测量库。</p><p><a href="https://github.com/pytorch/elastic" target="_blank" rel="noopener">PyTorch Elastic</a></p><p>一个能够以容错和弹性的方式执行分布式训练任务的框架。</p><p><a href="https://github.com/EmbarkStudios/blender-tools" target="_blank" rel="noopener">blender-tools</a></p><p>包含工作流工具，用于游戏开发的 Blender 插件。</p><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;新鲜事儿&quot;&gt;&lt;a href=&quot;#新鲜事儿&quot; class=&quot;headerlink&quot; title=&quot;新鲜事儿&quot;&gt;&lt;/a&gt;新鲜事儿&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://pytorch.org/blog/openmined-and-pytorch-launch-fellowship-funding-for-privacy-preserving-ml/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OpenMined 和 PyTorch 合作启动为保护隐私的 ML 社区提供研究金&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PyTorch 团队已投资 25 万美元来支持 OpenMined 进一步发展和扩大隐私保护的 ML。你可以通过三种不同的机会参与该项目的开发。这些奖学金的每一项都进一步促进了我们的共同使命，即降低隐私保护机器学习的进入障碍，并创建一个更具隐私保护的世界。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/categories/Python/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/tags/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
  </entry>
  
  <entry>
    <title>Python 命令行之旅：使用 click 实现 git 命令</title>
    <link href="http://prodesire.cn/2019/12/14/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E4%BD%BF%E7%94%A8-click-%E5%AE%9E%E7%8E%B0-git-%E5%91%BD%E4%BB%A4/"/>
    <id>http://prodesire.cn/2019/12/14/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E4%BD%BF%E7%94%A8-click-%E5%AE%9E%E7%8E%B0-git-%E5%91%BD%E4%BB%A4/</id>
    <published>2019-12-14T08:31:11.000Z</published>
    <updated>2020-07-24T15:00:04.356Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在前面五篇介绍 <code>click</code> 的文章中，我们全面了解了 <code>click</code> 的强大能力。按照惯例，我们要像使用 <code>argparse</code> 和 <code>docopt</code> 一样使用 <code>click</code> 来实现 git 命令。</p><p>本文的关注点并不在 <code>git</code> 的各种命令是如何实现的，而是怎么使用 <code>click</code> 去打造一个实用命令行程序，代码结构是怎样的。因此，和 <code>git</code> 相关的操作，将会使用 <code>gitpython</code> 库来简单实现。</p><p>为了让没读过 <code>使用 xxx 实现 git 命令</code>（<code>xxx</code> 指 <code>argparse</code> 和 <code>docopt</code>） 的小伙伴也能读明白本文，我们仍会对 <code>git</code> 常用命令和 <code>gitpython</code> 做一个简单介绍。</p><a id="more"></a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">本系列文章默认使用 Python 3 作为解释器进行讲解。</span><br><span class="line">若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~</span><br></pre></td></tr></table></figure><h2 id="git-常用命令"><a href="#git-常用命令" class="headerlink" title="git 常用命令"></a>git 常用命令</h2><p>当你写好一段代码或增删一些文件后，会用如下命令查看文件状态：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br></pre></td></tr></table></figure><p>确认文件状态后，会用如下命令将的一个或多个文件（夹）添加到暂存区：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git add [pathspec [pathspec ...]]</span><br></pre></td></tr></table></figure><p>然后使用如下命令提交信息：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git commit -m <span class="string">"your commit message"</span></span><br></pre></td></tr></table></figure><p>最后使用如下命令将提交推送到远程仓库：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push</span><br></pre></td></tr></table></figure><p>我们将使用 <code>click</code> 和 <code>gitpython</code> 库来实现这 4 个子命令。</p><h2 id="关于-gitpython"><a href="#关于-gitpython" class="headerlink" title="关于 gitpython"></a>关于 gitpython</h2><p><a href="https://gitpython.readthedocs.io/en/stable/intro.html" target="_blank" rel="noopener">gitpython</a> 是一个和 <code>git</code> 仓库交互的 Python 第三方库。<br>我们将借用它的能力来实现真正的 <code>git</code> 逻辑。</p><p>安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install gitpython</span><br></pre></td></tr></table></figure><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p>在实现前，我们不妨先思考下会用到 <code>click</code> 的哪些功能？整个程序的结构是怎样的？</p><p><strong>click</strong></p><p><code>git</code> 的 4 个子命令的实现其实对应于四个函数，每个函数使用 <code>click</code> 的 <code>command</code> 来装饰。<br>而对于 <code>git add</code> 和 <code>git commit</code>，则分别需要表示参数的 <code>click.argument</code> 和表示选项的 <code>click.option</code> 来装饰。</p><p><strong>程序结构</strong></p><p>程序结构上：</p><ul><li>实例化 <code>Git</code> 对象，供全局使用</li><li>定义 <code>cli</code> 函数作为命令组，也就是整个命令程序的入口</li><li>定义四个命令对应的实现函数 <code>status</code>、<code>add</code>、<code>commit</code>、<code>push</code></li></ul><p>则基本结构如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> click</span><br><span class="line"><span class="keyword">from</span> git.cmd <span class="keyword">import</span> Git</span><br><span class="line"></span><br><span class="line">git = Git(os.getcwd())</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@click.group()</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">cli</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    git 命令行</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">status</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 status 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="meta">@click.argument('pathspec', nargs=-1)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(pathspec)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 add 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="meta">@click.option('-m', 'msg')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">commit</span><span class="params">(msg)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 -m &lt;msg&gt; 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">push</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 push 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    cli()</span><br></pre></td></tr></table></figure><p>下面我们将一步步地实现我们的 <code>git</code> 程序。</p><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p>假定我们在 <a href="https://github.com/HelloGitHub-Team/Article/blob/master/contents/Python/cmdline/click-git.py" target="_blank" rel="noopener">click-git.py</a> 文件中实现我们的 <code>git</code> 程序。</p><h3 id="status-子命令"><a href="#status-子命令" class="headerlink" title="status 子命令"></a>status 子命令</h3><p><code>status</code> 子命令不接受任何参数和选项，因此其实现函数只需 <code>cli.command()</code> 装饰。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">status</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 status 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    cmd = [<span class="string">'git'</span>, <span class="string">'status'</span>]</span><br><span class="line">    output = git.execute(cmd)</span><br><span class="line">    click.echo(output)</span><br></pre></td></tr></table></figure><p>不难看出，我们最后调用了真正的 <code>git status</code> 来实现，并打印了输出。</p><h3 id="add-子命令"><a href="#add-子命令" class="headerlink" title="add 子命令"></a>add 子命令</h3><p><code>add</code> 子命令相对于 <code>status</code> 子命令，需要接受任意个 pathspec 参数，因此增加一个 <code>click.argument</code> 装饰器，并且在 <code>add</code> 函数中需要增加同名的 <code>pathspec</code> 入参。<br>经 <code>click</code> 处理后的 <code>pathspec</code> 其实是个元组，和列表相加前，需要先转换为列表。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="meta">@click.argument('pathspec', nargs=-1)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(pathspec)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 add 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    cmd = [<span class="string">'git'</span>, <span class="string">'add'</span>] + list(pathspec)</span><br><span class="line">    output = git.execute(cmd)</span><br><span class="line">    click.echo(output)</span><br></pre></td></tr></table></figure><p>当我们执行 <code>python3 click-git.py add --help</code> 时，结果如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Usage: click-git.py add [OPTIONS] [PATHSPEC]...</span><br><span class="line"></span><br><span class="line">  处理 add 命令</span><br><span class="line"></span><br><span class="line">Options:</span><br><span class="line">  --help  Show this message and exit.</span><br></pre></td></tr></table></figure><p>既然 <code>git add</code> 能接受任意多个 <code>pathspec</code>，那么 <code>add(pathspec)</code> 的参数其实改为复数形式更为合适，但我们又希望帮助信息中是单数形式，这就需要额外指定 <code>metavar</code>，则有：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="meta">@click.argument('pathspecs', nargs=-1, metavar='[PATHSPEC]...')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add</span><span class="params">(pathspecs)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 add 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    cmd = [<span class="string">'git'</span>, <span class="string">'add'</span>] + list(pathspecs)</span><br><span class="line">    output = git.execute(cmd)</span><br><span class="line">    click.echo(output)</span><br></pre></td></tr></table></figure><h3 id="commit-子命令"><a href="#commit-子命令" class="headerlink" title="commit 子命令"></a>commit 子命令</h3><p><code>add</code> 子命令相对于 <code>status</code> 子命令，需要接受 <code>-m</code> 选项，因此增加一个 <code>click.option</code> 装饰器，指定选项名称 <code>msg</code>，并且在 <code>commit</code> 函数中增加同名入参。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="meta">@click.option('-m', 'msg')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">commit</span><span class="params">(msg)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 -m &lt;msg&gt; 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    cmd = [<span class="string">'git'</span>, <span class="string">'commit'</span>, <span class="string">'-m'</span>, msg]</span><br><span class="line">    output = git.execute(cmd)</span><br><span class="line">    click.echo(output)</span><br></pre></td></tr></table></figure><h3 id="push-子命令"><a href="#push-子命令" class="headerlink" title="push 子命令"></a>push 子命令</h3><p><code>push</code> 子命令同 <code>status</code> 子命令一样，不接受任何参数和选项，因此其实现函数只需 <code>cli.command()</code> 装饰。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@cli.command()</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">push</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    处理 push 命令</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    cmd = [<span class="string">'git'</span>, <span class="string">'push'</span>]</span><br><span class="line">    output = git.execute(cmd)</span><br><span class="line">    click.echo(output)</span><br></pre></td></tr></table></figure><p>至此，我们就实现了一个简单的 <code>git</code> 命令行，使用 <code>python click-git.py status</code> 便可查询项目状态。</p><p>非常方便的是，每个命令函数的 <code>docstring</code> 都将作为这个命令的帮助信息，因此，当我们执行 <code>python3 click-git.py --help</code> 会自动生成如下帮助内容：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Usage: click-git.py [OPTIONS] COMMAND [ARGS]...</span><br><span class="line"></span><br><span class="line">  git 命令行</span><br><span class="line"></span><br><span class="line">Options:</span><br><span class="line">  --help  Show this message and exit.</span><br><span class="line"></span><br><span class="line">Commands:</span><br><span class="line">  add     处理 add 命令</span><br><span class="line">  commit  处理 -m &lt;msg&gt; 命令</span><br><span class="line">  push    处理 push 命令</span><br><span class="line">  status  处理 status 命令</span><br></pre></td></tr></table></figure><p>想看整个源码，请戳 <a href="https://github.com/HelloGitHub-Team/Article/blob/master/contents/Python/cmdline/click-git.py" target="_blank" rel="noopener">click-git.py</a> 。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>本文简单介绍了日常工作中常用的 <code>git</code> 命令，然后提出实现它的思路，最终一步步地使用 <code>click</code> 和 <code>gitpython</code> 实现了 <code>git</code> 程序。</p><p>对比 <code>argparse</code> 和 <code>click</code> 的实现版本，你会发现使用 <code>click</code> 来实现变得特定简单：</p><ul><li>相较于 <code>argparse</code>，子解析器、参数类型什么的统统不需要关心</li><li>相较于 <code>docopt</code>，参数解析和命令调用处理也不需要关心</li></ul><p>这无疑是 <code>click</code> 最大的优势了。</p><p>关于 <code>click</code> 的讲解将告一段落，回顾下 <code>click</code> 的至简之道，你会爱上它。</p><p>现在，你已学会了三个命令行解析库的使用了。但你以为这就够了吗？<code>click</code> 已经够简单了吧，够直接了吧？但它仍然不是最简单的。</p><p>在下篇文章中，将为大家介绍一个由谷歌出品的在 Python 界很火的命令行库 —— <code>fire</code>。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ul><li><a href="/2019/08/13/Python-命令行之旅：初探-argparse/" title="Python 命令行之旅：初探 argparse">Python 命令行之旅：初探 argparse</a></li><li><a href="/2019/08/20/Python-命令行之旅：深入-argparse（一）/" title="Python 命令行之旅：深入 argparse（一）">Python 命令行之旅：深入 argparse（一）</a></li><li><a href="/2019/08/27/Python-命令行之旅：深入-argparse（二）/" title="Python 命令行之旅：深入 argparse（二）">Python 命令行之旅：深入 argparse（二）</a></li><li><a href="/2019/09/04/Python-命令行之旅：使用-argparse-实现-git-命令/" title="Python 命令行之旅：使用 argparse 实现 git 命令">Python 命令行之旅：使用 argparse 实现 git 命令</a></li><li><a href="/2019/10/09/Python-命令行之旅：初探-docopt/" title="Python 命令行之旅：初探 docopt">Python 命令行之旅：初探 docopt</a></li><li><a href="/2019/10/15/Python-命令行之旅：深入-docopt/" title="Python 命令行之旅：深入 docopt">Python 命令行之旅：深入 docopt</a></li><li><a href="/2019/10/30/Python-命令行之旅：使用-docopt-实现-git-命令/" title="Python 命令行之旅：使用 docopt 实现 git 命令">Python 命令行之旅：使用 docopt 实现 git 命令</a></li><li><a href="/2019/11/04/Python-命令行之旅：初探-click/" title="Python 命令行之旅：初探 click">Python 命令行之旅：初探 click</a></li><li><a href="/2019/11/11/Python-命令行之旅：深入-click（一）/" title="Python 命令行之旅：深入 click（一）">Python 命令行之旅：深入 click（一）</a></li><li><a href="/2019/11/17/Python-命令行之旅：深入-click（二）/" title="Python 命令行之旅：深入 click（二）">Python 命令行之旅：深入 click（二）</a></li><li><a href="/2019/11/25/Python-命令行之旅：深入-click（三）/" title="Python 命令行之旅：深入 click（三）">Python 命令行之旅：深入 click（三）</a></li><li><a href="/2019/12/09/Python-命令行之旅：深入-click（四）/" title="Python 命令行之旅：深入 click（四）">Python 命令行之旅：深入 click（四）</a></li><li><a href="/2019/12/14/Python-命令行之旅：使用-click-实现-git-命令/" title="Python 命令行之旅：使用 click 实现 git 命令">Python 命令行之旅：使用 click 实现 git 命令</a></li><li><a href="/2019/12/22/Python-命令行之旅：初探-fire/" title="Python 命令行之旅：初探 fire">Python 命令行之旅：初探 fire</a></li><li><a href="/2019/12/29/Python-命令行之旅：深入-fire（一）/" title="Python 命令行之旅：深入 fire（一）">Python 命令行之旅：深入 fire（一）</a></li><li><a href="/2020/01/05/Python-命令行之旅：深入-fire（二）/" title="Python 命令行之旅：深入 fire（二）">Python 命令行之旅：深入 fire（二）</a></li><li><a href="/2020/01/12/Python-命令行之旅：使用-fire-实现-git-命令/" title="Python 命令行之旅：使用 fire 实现 git 命令">Python 命令行之旅：使用 fire 实现 git 命令</a></li><li><a href="/2020/02/09/Python-命令行之旅：argparse、docopt、click-和-fire-总结篇/" title="Python 命令行之旅：argparse、docopt、click 和 fire 总结篇">Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</a></li><li><a href="/2020/07/02/Python-命令行大乱斗/" title="Python 命令行大乱斗">Python 命令行大乱斗</a></li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;在前面五篇介绍 &lt;code&gt;click&lt;/code&gt; 的文章中，我们全面了解了 &lt;code&gt;click&lt;/code&gt; 的强大能力。按照惯例，我们要像使用 &lt;code&gt;argparse&lt;/code&gt; 和 &lt;code&gt;docopt&lt;/code&gt; 一样使用 &lt;code&gt;click&lt;/code&gt; 来实现 git 命令。&lt;/p&gt;
&lt;p&gt;本文的关注点并不在 &lt;code&gt;git&lt;/code&gt; 的各种命令是如何实现的，而是怎么使用 &lt;code&gt;click&lt;/code&gt; 去打造一个实用命令行程序，代码结构是怎样的。因此，和 &lt;code&gt;git&lt;/code&gt; 相关的操作，将会使用 &lt;code&gt;gitpython&lt;/code&gt; 库来简单实现。&lt;/p&gt;
&lt;p&gt;为了让没读过 &lt;code&gt;使用 xxx 实现 git 命令&lt;/code&gt;（&lt;code&gt;xxx&lt;/code&gt; 指 &lt;code&gt;argparse&lt;/code&gt; 和 &lt;code&gt;docopt&lt;/code&gt;） 的小伙伴也能读明白本文，我们仍会对 &lt;code&gt;git&lt;/code&gt; 常用命令和 &lt;code&gt;gitpython&lt;/code&gt; 做一个简单介绍。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/categories/Python/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
      <category term="click" scheme="http://prodesire.cn/tags/click/"/>
    
  </entry>
  
  <entry>
    <title>Python 壹周刊 004</title>
    <link href="http://prodesire.cn/2019/12/10/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-004/"/>
    <id>http://prodesire.cn/2019/12/10/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-004/</id>
    <published>2019-12-10T12:00:00.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="新鲜事儿"><a href="#新鲜事儿" class="headerlink" title="新鲜事儿"></a>新鲜事儿</h2><p><a href="https://www.zdnet.com/article/two-malicious-python-libraries-removed-from-pypi/" target="_blank" rel="noopener">两个恶意 Python 库被发现窃取 SSH 和 GPG 密钥</a></p><p>通过相似字母来让假库和真库看起来一样，以误导使用者。安装库时务必小心检查。</p><a id="more"></a><p><a href="https://metaflow.org/" target="_blank" rel="noopener">Netflix 开源了用于数据科学项目管理的 Python 库 —— Metaflow</a></p><p>Metaflow 是 Netflix 机器学习基础架构的关键部件，主要用于加速数据科学工作流的构建和部署，Netflix 希望通过开源 Metaflow 简化机器学习项目从原型阶段到生产阶段的过程，进而提高数据科学家的工作效率。</p><p><a href="https://pyfound.blogspot.com/2019/12/moss-czi-support-pip.html" target="_blank" rel="noopener">Mozilla 和 Chan Zuckerberg Initiative 支持 pip</a></p><p>Python 软件基金会（Python Software Foundation）将获得 407,000 美元，以支持 2020 年的 pip 改进工作。这项基础性的变革性工作将使 Python 开发人员和用户专注于他们正在构建和使用的工具，而不是对依赖冲突进行故障排查。让我们期待一下吧~</p><h2 id="好文共赏"><a href="#好文共赏" class="headerlink" title="好文共赏"></a>好文共赏</h2><p><a href="https://blog.startifact.com/posts/framework-patterns.html" target="_blank" rel="noopener">框架模式</a></p><p>有很多方法可以配置框架，而每种方法都有其自身的权衡。 这篇文章描述了 N 种框架配置模式，提供了简短的示例和并权衡点。非常值得一看。</p><p><a href="https://ahmedbesbes.com/end-to-end-ml.html" target="_blank" rel="noopener">端到端机器学习：从数据收集到部署</a></p><p>文章列出完成构建和部署机器学习应用程序的必要步骤。 从数据收集到部署，将会是一段令人兴奋且有趣的旅程。</p><p><a href="https://strangemachines.io/articles/performant-python" target="_blank" rel="noopener">高性能 Python</a></p><p>简单几行代码，你也能写出高性能的 Python 程序。</p><p><a href="https://dev.to/duomly/a-few-useful-tips-on-how-to-practice-python-5a9" target="_blank" rel="noopener">有关如何练习 Python 的一些有用技巧</a></p><p>文章提出了诸如选择合适的环境、编写和改进代码、分析源码、成为社区一部分等等技巧来帮助你提升 Python 技能。</p><p><a href="https://dev.to/steelwolf180/developer-tools-frameworks-for-a-python-developer-5919" target="_blank" rel="noopener">适用于 Python 开发人员的开发人员工具和框架</a></p><p>文章列出的工具和框架可以说是 Python 开发人员必备的了。</p><p><a href="https://www.pyimagesearch.com/2019/12/02/opencv-vehicle-detection-tracking-and-speed-estimation/" target="_blank" rel="noopener">使用 OpenCV 进行车辆检测、追踪和速度估算</a></p><p>你将学习如何使用 OpenCV 和深度学习来检测视频流中的车辆，对其进行跟踪，并应用速度估算来检测行驶中的车辆的 MPH/KPH。</p><p><a href="https://www.dataquest.io/blog/excel-vs-python/" target="_blank" rel="noopener">Excel vs Python：如何进行常见的数据分析任务</a></p><p>Excel 和 Python 有什么区别？ 在本教程中，我们将通过比较如何在两个平台上执行基本的分析任务进行比较。</p><p><a href="https://binaroid.com/blog/django-centralised-logging-using-elasticsearch-logstash-kibana-elk-filebeat" target="_blank" rel="noopener">使用 Elasticsearch、Logstash、Kibana（ELK）+ Filebeat 实现 Django 的集中式日志记录</a></p><p>在本教程中，我们将学习如何将应用程序日志从 Django 应用程序推送到 Elasticsearch 存储，并能够在 Kibana Web 工具中以可读的方式显示它。本文的主要目的是使用 Elastic 提供的另一个工具（Filebeat ）在 Django 服务器和 ELK 栈（Elasticsearch、Kibana 和 Logstash）之间建立连接。 我们还将简要介绍所有前面的步骤，例如日志记录背后的原因，在 Django 中配置日志记录以及安装 ELK 栈。</p><p><a href="https://pbpython.com/windows-shortcut.html" target="_blank" rel="noopener">使用 Python 构建 Windows 快捷方式</a></p><p>作者花了太多时间试图在多台 Windows 计算机上正确设置快捷方式，以至于要自动化创建链接。 本文将讨论如何使用 Python 创建自定义 Windows 快捷方式来启动 conda 环境。</p><h2 id="赞视频"><a href="#赞视频" class="headerlink" title="赞视频"></a>赞视频</h2><p><a href="https://www.youtube.com/watch?v=5sEm7RcRF_g&amp;feature=youtu.be" target="_blank" rel="noopener">通过 gmaps 玩转谷歌地图</a></p><p>使用 jupyter 演示，手把手教你如何玩转谷歌地图</p><p><a href="https://www.youtube.com/watch?v=aPCZcv-5qfA" target="_blank" rel="noopener">量子计算机编程</a></p><p>使用 IBM 免费的基于云的量子机器和 Qiskit，对量子计算机编程进行实用的入门介绍。</p><p><a href="https://www.youtube.com/watch?v=_BBNVFirvTY" target="_blank" rel="noopener">Django 3.0 都有哪些新功能</a></p><p>Django 刚刚发布了新的主要版本 Django 3.0。 它对你有何影响？什么是 ASGI？ 让视频中的小哥哥告诉你</p><p><a href="https://www.youtube.com/watch?v=mE0oR9NQefw" target="_blank" rel="noopener">Python 字节码入门教程</a></p><p>这是一种很好的逐步深入 CPython（事实上的 Python 参考实现）内部的方法。 如果你想了解有关 Python 的更多信息，请花 10 分钟！</p><p><a href="https://www.youtube.com/playlist?list=PLQYPYhKQVTvetDJZFGY8RfYlPBLQmbt-T" target="_blank" rel="noopener">PyCon 瑞典 2019 视频</a></p><h2 id="酷开源"><a href="#酷开源" class="headerlink" title="酷开源"></a>酷开源</h2><p><a href="https://mardix.github.io/assembly/" target="_blank" rel="noopener">Assembly</a></p><p>一个基于 Flask 的 Pythonic 且面向对象的 Web 框架。</p><p><a href="https://github.com/enric1994/emoji_trends" target="_blank" rel="noopener">emoji_trends</a></p><p>Twitter 上的 emoji 是如何被使用的。</p><p><a href="https://github.com/adamcharnock/lightbus/" target="_blank" rel="noopener">lightbus</a></p><p>Python 3 的 RPC &amp; 事件框架。</p><p><a href="https://github.com/rapidsai/cusignal" target="_blank" rel="noopener">cuSignal </a></p><p>cuSignal 使用 CuPy（GPU 加速的 NumPy）和自定义的 Numba CUDA 内核来加速流行的 SciPy Signal 库。</p><p><a href="https://github.com/aroberge/friendly-traceback" target="_blank" rel="noopener">friendly-traceback</a></p><p>面向 Python 初学者：用更易于理解的东西（可以翻译成多种语言）代替标准回溯。</p><p><a href="https://github.com/TokenChingy/pytasking" target="_blank" rel="noopener">pytasking</a></p><p>一个简单的 Python 3.5+多任务库。</p><p><a href="https://github.com/OfirKP/Whatsapp-Net" target="_blank" rel="noopener">Whatsapp-Net</a></p><p>根据 WhatsApp 组数据生成网络连接图。</p><p><a href="https://github.com/RaRe-Technologies/gensim" target="_blank" rel="noopener">gensim</a></p><p>用于主题建模、文档索引和相似性检索的 Python 库。</p><p><a href="https://github.com/micropython/micropython" target="_blank" rel="noopener">micropython</a></p><p>适用于微控制器和受限系统的精简高效的 Python 实现 。</p><p><a href="https://github.com/facebook/prophet" target="_blank" rel="noopener">prophet</a></p><p>为具有多季节性、线性或非线性增长的时间序列数据生成高质量的预测。</p><p><a href="https://github.com/tqdm/tqdm" target="_blank" rel="noopener">tqdm</a></p><p>适用于 Python 和 CLI 的快速、可扩展的进度栏。</p><p><a href="https://github.com/jaraco/keyring" target="_blank" rel="noopener">keyring</a></p><p>提供了一种从 Python 访问系统密钥环服务的简便方法。</p><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;新鲜事儿&quot;&gt;&lt;a href=&quot;#新鲜事儿&quot; class=&quot;headerlink&quot; title=&quot;新鲜事儿&quot;&gt;&lt;/a&gt;新鲜事儿&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://www.zdnet.com/article/two-malicious-python-libraries-removed-from-pypi/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;两个恶意 Python 库被发现窃取 SSH 和 GPG 密钥&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;通过相似字母来让假库和真库看起来一样，以误导使用者。安装库时务必小心检查。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/categories/Python/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/tags/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
  </entry>
  
  <entry>
    <title>Python 命令行之旅：深入 click（四）</title>
    <link href="http://prodesire.cn/2019/12/09/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E6%B7%B1%E5%85%A5-click%EF%BC%88%E5%9B%9B%EF%BC%89/"/>
    <id>http://prodesire.cn/2019/12/09/Python-%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B9%8B%E6%97%85%EF%BC%9A%E6%B7%B1%E5%85%A5-click%EF%BC%88%E5%9B%9B%EF%BC%89/</id>
    <published>2019-12-08T17:51:25.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在前面三篇文章中，我们介绍了 <code>click</code> 中的参数、选项和命令，本文将介绍 <code>click</code> 锦上添花的功能，以帮助我们更加轻松地打造一个更加强大的命令行程序。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">本系列文章默认使用 Python 3 作为解释器进行讲解。</span><br><span class="line">若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~</span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="增强功能"><a href="#增强功能" class="headerlink" title="增强功能"></a>增强功能</h2><h3 id="Bash-补全"><a href="#Bash-补全" class="headerlink" title="Bash 补全"></a>Bash 补全</h3><p>Bash 补全是 <code>click</code> 提供的一个非常便捷和强大的功能，这是它比 <code>argpase</code> 和 <code>docopt</code> 强大的一个表现。</p><p>在命令行程序正确安装后，Bash 补全才可以使用。而如何安装可以参考 <a href="https://click.palletsprojects.com/en/7.x/setuptools/#setuptools-integration" target="_blank" rel="noopener">setup 集成</a>。Click 目前仅支持 Bash 和 Zsh 的补全。</p><h4 id="补全能力"><a href="#补全能力" class="headerlink" title="补全能力"></a>补全能力</h4><p>通常来说，Bash 补全支持对子命令、选项、以及选项或参数值得补全。比如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ repo &lt;TAB&gt;&lt;TAB&gt;</span><br><span class="line">clone    commit   copy     delete   setuser</span><br><span class="line">$ repo clone -&lt;TAB&gt;&lt;TAB&gt;</span><br><span class="line">--deep     --help     --rev      --shallow  -r</span><br></pre></td></tr></table></figure><p>此外，<code>click</code> 还支持自定义补全，这在动态生成补全场景中很有用，使用 <code>autocompletion</code> 参数。<code>autocompletion</code> 需要指定为一个回调函数，并且返回字符串的列表。此函数接受三个参数：</p><ul><li><code>ctx</code> —— 当前的 click 上下文</li><li><code>args</code> 传入的参数列表</li><li><code>incomplete</code> 正在补全的词</li></ul><p>这里有一个根据环境变量动态生成补全的示例：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><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="function"><span class="keyword">def</span> <span class="title">get_env_vars</span><span class="params">(ctx, args, incomplete)</span>:</span></span><br><span class="line">    <span class="keyword">return</span> [k <span class="keyword">for</span> k <span class="keyword">in</span> os.environ.keys() <span class="keyword">if</span> incomplete <span class="keyword">in</span> k]</span><br><span class="line"></span><br><span class="line"><span class="meta">@click.command()</span></span><br><span class="line"><span class="meta">@click.argument("envvar", type=click.STRING, autocompletion=get_env_vars)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">cmd1</span><span class="params">(envvar)</span>:</span></span><br><span class="line">    click.echo(<span class="string">'Environment variable: %s'</span> % envvar)</span><br><span class="line">    click.echo(<span class="string">'Value: %s'</span> % os.environ[envvar])</span><br></pre></td></tr></table></figure><p>在 <code>ZSH</code> 中，还支持补全帮助信息。只需将 <code>autocompletion</code> 回调函数中返回的字符串列表中的字符串改为二元元组，第一个元素是补全内容，第二个元素是帮助信息。</p><p>这里有一个颜色补全的示例：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><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="function"><span class="keyword">def</span> <span class="title">get_colors</span><span class="params">(ctx, args, incomplete)</span>:</span></span><br><span class="line">    colors = [(<span class="string">'red'</span>, <span class="string">'help string for the color red'</span>),</span><br><span class="line">              (<span class="string">'blue'</span>, <span class="string">'help string for the color blue'</span>),</span><br><span class="line">              (<span class="string">'green'</span>, <span class="string">'help string for the color green'</span>)]</span><br><span class="line">    <span class="keyword">return</span> [c <span class="keyword">for</span> c <span class="keyword">in</span> colors <span class="keyword">if</span> incomplete <span class="keyword">in</span> c[<span class="number">0</span>]]</span><br><span class="line"></span><br><span class="line"><span class="meta">@click.command()</span></span><br><span class="line"><span class="meta">@click.argument("color", type=click.STRING, autocompletion=get_colors)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">cmd1</span><span class="params">(color)</span>:</span></span><br><span class="line">    click.echo(<span class="string">'Chosen color is %s'</span> % color)</span><br></pre></td></tr></table></figure><h4 id="激活补全"><a href="#激活补全" class="headerlink" title="激活补全"></a>激活补全</h4><p>要激活 Bash 的补全功能，就需要告诉它你的命令行程序有补全的能力。通常通过一个神奇的环境变量 <code>_&lt;PROG_NAME&gt;_COMPLETE</code> 来告知，其中 <code>&lt;PROG_NAME&gt;</code> 是大写下划线形式的程序名称。</p><p>比如有一个命令行程序叫做 <code>foo-bar</code>，那么对应的环境变量名称为 <code>_FOO_BAR_COMPLETE</code>，然后在 <code>.bashrc</code> 中使用 <code>source</code> 导出即可：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">eval</span> <span class="string">"<span class="variable">$(_FOO_BAR_COMPLETE=source foo-bar)</span>"</span></span><br><span class="line">···</span><br><span class="line"></span><br><span class="line">或者在 `.zshrc` 中使用：</span><br><span class="line">```bash</span><br><span class="line"><span class="built_in">eval</span> <span class="string">"<span class="variable">$(_FOO_BAR_COMPLETE=source_zsh foo-bar)</span>"</span></span><br></pre></td></tr></table></figure><p>不过上面的方式总是在命令行程序启动时调用，这可能在有多个程序时减慢 shell 激活的速度。另一种方式是把命令放在文件中，就像这样：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 针对 Bash</span></span><br><span class="line">_FOO_BAR_COMPLETE=<span class="built_in">source</span> foo-bar &gt; foo-bar-complete.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 针对 ZSH</span></span><br><span class="line">_FOO_BAR_COMPLETE=source_zsh foo-bar &gt; foo-bar-complete.sh</span><br></pre></td></tr></table></figure><p>然后把脚本文件路径加到 <code>.bashrc</code> 或 <code>.zshrc</code> 中：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">. /path/to/foo-bar-complete.sh</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><a href="https://click.palletsprojects.com/en/7.x/api/#click.echo" target="_blank" rel="noopener">echo()</a> 函数可以说是最有用的实用工具了。它和 Python 的 <code>print</code> 类似，主要的区别在于它同时在 Python 2 和 3 中生效，能够智能地检测未配置正确的输出流，且几乎不会失败（除了 Python 3 中的<a href="https://click.palletsprojects.com/en/7.x/python3/#python3-limitations" target="_blank" rel="noopener">少数限制</a>。）</p><p><code>echo</code> 即支持 unicode，也支持二级制数据，如：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> click</span><br><span class="line"></span><br><span class="line">click.echo(<span class="string">'Hello World!'</span>)</span><br><span class="line"></span><br><span class="line">click.echo(<span class="string">b'\xe2\x98\x83'</span>, nl=<span class="keyword">False</span>) <span class="comment"># nl=False 表示不输出换行符</span></span><br></pre></td></tr></table></figure><h4 id="ANSI-颜色"><a href="#ANSI-颜色" class="headerlink" title="ANSI 颜色"></a>ANSI 颜色</h4><p>有些时候你可能希望输出是有颜色的，这尤其在输出错误信息时有用，而 <code>click</code> 在这方面支持的很好。</p><p>首先，你需要安装 <code>colorama</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install colorama</span><br></pre></td></tr></table></figure><p>然后，就可以使用 <a href="https://click.palletsprojects.com/en/7.x/api/#click.style" target="_blank" rel="noopener">style()</a> 函数来指定颜色：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> click</span><br><span class="line"></span><br><span class="line">click.echo(click.style(<span class="string">'Hello World!'</span>, fg=<span class="string">'green'</span>))</span><br><span class="line">click.echo(click.style(<span class="string">'Some more text'</span>, bg=<span class="string">'blue'</span>, fg=<span class="string">'white'</span>))</span><br><span class="line">click.echo(click.style(<span class="string">'ATTENTION'</span>, blink=<span class="keyword">True</span>, bold=<span class="keyword">True</span>))</span><br></pre></td></tr></table></figure><p><code>click</code> 还提供了更加简便的函数 <a href="https://click.palletsprojects.com/en/7.x/api/#click.secho" target="_blank" rel="noopener">secho</a>，它就是 <code>echo</code> 和 <code>style</code> 的组合：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">click.secho(<span class="string">'Hello World!'</span>, fg=<span class="string">'green'</span>)</span><br><span class="line">click.secho(<span class="string">'Some more text'</span>, bg=<span class="string">'blue'</span>, fg=<span class="string">'white'</span>)</span><br><span class="line">click.secho(<span class="string">'ATTENTION'</span>, blink=<span class="keyword">True</span>, bold=<span class="keyword">True</span>)</span><br></pre></td></tr></table></figure><h4 id="分页支持"><a href="#分页支持" class="headerlink" title="分页支持"></a>分页支持</h4><p>有些时候，命令行程序会输出长文本，但你希望能让用户盘也浏览。使用 <a href="https://click.palletsprojects.com/en/7.x/api/#click.echo_via_pager" target="_blank" rel="noopener">echo_via_pager()</a> 函数就可以轻松做到。</p><p>例如：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">less</span><span class="params">()</span>:</span></span><br><span class="line">    click.echo_via_pager(<span class="string">'\n'</span>.join(<span class="string">'Line %d'</span> % idx</span><br><span class="line">                                   <span class="keyword">for</span> idx <span class="keyword">in</span> range(<span class="number">200</span>)))</span><br></pre></td></tr></table></figure><p>如果输出的文本特别大，处于性能的考虑，希望翻页时生成对应内容，那么就可以使用生成器：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">_generate_output</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">for</span> idx <span class="keyword">in</span> range(<span class="number">50000</span>):</span><br><span class="line">        <span class="keyword">yield</span> <span class="string">"Line %d\n"</span> % idx</span><br><span class="line"></span><br><span class="line"><span class="meta">@click.command()</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">less</span><span class="params">()</span>:</span></span><br><span class="line">    click.echo_via_pager(_generate_output())</span><br></pre></td></tr></table></figure><h4 id="清除屏幕"><a href="#清除屏幕" class="headerlink" title="清除屏幕"></a>清除屏幕</h4><p>使用 <a href="https://click.palletsprojects.com/en/7.x/api/#click.clear" target="_blank" rel="noopener">clear()</a> 可以轻松清除屏幕内容：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> click</span><br><span class="line">click.clear()</span><br></pre></td></tr></table></figure><h4 id="从终端获取字符"><a href="#从终端获取字符" class="headerlink" title="从终端获取字符"></a>从终端获取字符</h4><p>通常情况下，使用内建函数 <code>input</code> 或 <code>raw_input</code> 获得的输入是用户输出一段字符然后回车得到的。但在有些场景下，你可能想在用户输入单个字符时就能获取到并且做一定的处理，这个时候 <a href="https://click.palletsprojects.com/en/7.x/api/#click.getchar" target="_blank" rel="noopener">getchar()</a> 就派上了用场。</p><p>比如，根据输入的 <code>y</code> 或 <code>n</code> 做特定处理：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> click</span><br><span class="line"></span><br><span class="line">click.echo(<span class="string">'Continue? [yn] '</span>, nl=<span class="keyword">False</span>)</span><br><span class="line">c = click.getchar()</span><br><span class="line">click.echo()</span><br><span class="line"><span class="keyword">if</span> c == <span class="string">'y'</span>:</span><br><span class="line">    click.echo(<span class="string">'We will go on'</span>)</span><br><span class="line"><span class="keyword">elif</span> c == <span class="string">'n'</span>:</span><br><span class="line">    click.echo(<span class="string">'Abort!'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    click.echo(<span class="string">'Invalid input :('</span>)</span><br></pre></td></tr></table></figure><h4 id="等待按键"><a href="#等待按键" class="headerlink" title="等待按键"></a>等待按键</h4><p>在 Windows 的 cmd 中我们经常看到当执行完一个命令后，提示按下任意键退出。通过使用 <a href="https://click.palletsprojects.com/en/7.x/api/#click.pause" target="_blank" rel="noopener">pause()</a> 可以实现暂停直至用户按下任意键：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> click</span><br><span class="line">click.pause()</span><br></pre></td></tr></table></figure><h4 id="启动编辑器"><a href="#启动编辑器" class="headerlink" title="启动编辑器"></a>启动编辑器</h4><p>通过 <a href="https://click.palletsprojects.com/en/7.x/api/#click.edit" target="_blank" rel="noopener">edit()</a> 可以自动启动编辑器。这在需要用户输入多行内容时十分有用。</p><p>在下面的示例中，会启动默认的文本编辑器，并在里面输入一段话：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> click</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_commit_message</span><span class="params">()</span>:</span></span><br><span class="line">    MARKER = <span class="string">'# Everything below is ignored\n'</span></span><br><span class="line">    message = click.edit(<span class="string">'\n\n'</span> + MARKER)</span><br><span class="line">    <span class="keyword">if</span> message <span class="keyword">is</span> <span class="keyword">not</span> <span class="keyword">None</span>:</span><br><span class="line">        <span class="keyword">return</span> message.split(MARKER, <span class="number">1</span>)[<span class="number">0</span>].rstrip(<span class="string">'\n'</span>)</span><br></pre></td></tr></table></figure><p><code>edit()</code> 函数还支持打开特定文件，比如：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> click</span><br><span class="line">click.edit(filename=<span class="string">'/etc/passwd'</span>)</span><br></pre></td></tr></table></figure><h4 id="启动应用程序"><a href="#启动应用程序" class="headerlink" title="启动应用程序"></a>启动应用程序</h4><p>通过 <a href="https://click.palletsprojects.com/en/7.x/api/#click.launch" target="_blank" rel="noopener">launch</a> 可以打开 URL 或文件类型所关联的默认应用程序。如果设置 <code>locate=True</code>，则可以启动文件管理器并自动选中特定文件。</p><p>示例：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 打开浏览器，访问 URL</span></span><br><span class="line">click.launch(<span class="string">"https://click.palletsprojects.com/"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用默认应用程序打开 txt 文件</span></span><br><span class="line">click.launch(<span class="string">"/my/downloaded/file.txt"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打开文件管理器，并自动选中 file.txt</span></span><br><span class="line">click.launch(<span class="string">"/my/downloaded/file.txt"</span>, locate=<span class="keyword">True</span>)</span><br></pre></td></tr></table></figure><h4 id="显示进度条"><a href="#显示进度条" class="headerlink" title="显示进度条"></a>显示进度条</h4><p><code>click</code> 内置了 <a href="https://click.palletsprojects.com/en/7.x/api/#click.progressbar" target="_blank" rel="noopener">progressbar()</a> 函数来方便地显示进度条。</p><p>它的用法也很简单，假定你有一个要处理的可迭代对象，处理完每一项就要输出一下进度，那么就有两种用法。</p><p>用法一：使用 <code>progressbar</code> 构造出 <code>bar</code> 对象，迭代 <code>bar</code> 对象来自动告知进度：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> click</span><br><span class="line"></span><br><span class="line">all_the_users_to_process = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>]</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">modify_the_user</span><span class="params">(user)</span>:</span></span><br><span class="line">    time.sleep(<span class="number">0.5</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> click.progressbar(all_the_users_to_process) <span class="keyword">as</span> bar:</span><br><span class="line">    <span class="keyword">for</span> user <span class="keyword">in</span> bar:</span><br><span class="line">        modify_the_user(user)</span><br></pre></td></tr></table></figure><p>用法二：使用 <code>progressbar</code> 构造出 <code>bar</code> 对象，迭代原始可迭代对象，并不断向 <code>bar</code> 更新进度：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> click</span><br><span class="line"></span><br><span class="line">all_the_users_to_process = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>]</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">modify_the_user</span><span class="params">(user)</span>:</span></span><br><span class="line">    time.sleep(<span class="number">0.5</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> click.progressbar(all_the_users_to_process) <span class="keyword">as</span> bar:</span><br><span class="line">    <span class="keyword">for</span> user <span class="keyword">in</span> enumerate(all_the_users_to_process):</span><br><span class="line">        modify_the_user(user)</span><br><span class="line">        bar.update(<span class="number">1</span>)</span><br></pre></td></tr></table></figure><h4 id="更多实用工具"><a href="#更多实用工具" class="headerlink" title="更多实用工具"></a>更多实用工具</h4><ul><li><a href="https://click.palletsprojects.com/en/7.x/utils/#printing-filenames" target="_blank" rel="noopener">打印文件名</a></li><li><a href="https://click.palletsprojects.com/en/7.x/utils/#standard-streams" target="_blank" rel="noopener">标准流</a></li><li><a href="https://click.palletsprojects.com/en/7.x/utils/#intelligent-file-opening" target="_blank" rel="noopener">智能打开文件</a></li><li><a href="https://click.palletsprojects.com/en/7.x/utils/#finding-application-folders" target="_blank" rel="noopener">查找应用程序文件夹</a></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>click</code> 提供了非常多的增强型功能，本文着重介绍了它的 Bash 补全和十多个实用工具，这会让你在实现命令行的过程中如虎添翼。此外，<code>click</code> 还提供了诸如命令别名、参数修改、标准化令牌、调用其他命令、回调顺序等诸多<a href="https://click.palletsprojects.com/en/7.x/advanced/" target="_blank" rel="noopener">高级模式</a> 以应对更加复杂或特定的场景，我们就不再深入介绍。</p><p><code>click</code> 的介绍就告一段落，它将会是你编写命令行程序的一大利器。在下一篇文章中，我们依然会通过实现一个简单的 <code>git</code> 程序来进行 <code>click</code> 的实战。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ul><li><a href="/2019/08/13/Python-命令行之旅：初探-argparse/" title="Python 命令行之旅：初探 argparse">Python 命令行之旅：初探 argparse</a></li><li><a href="/2019/08/20/Python-命令行之旅：深入-argparse（一）/" title="Python 命令行之旅：深入 argparse（一）">Python 命令行之旅：深入 argparse（一）</a></li><li><a href="/2019/08/27/Python-命令行之旅：深入-argparse（二）/" title="Python 命令行之旅：深入 argparse（二）">Python 命令行之旅：深入 argparse（二）</a></li><li><a href="/2019/09/04/Python-命令行之旅：使用-argparse-实现-git-命令/" title="Python 命令行之旅：使用 argparse 实现 git 命令">Python 命令行之旅：使用 argparse 实现 git 命令</a></li><li><a href="/2019/10/09/Python-命令行之旅：初探-docopt/" title="Python 命令行之旅：初探 docopt">Python 命令行之旅：初探 docopt</a></li><li><a href="/2019/10/15/Python-命令行之旅：深入-docopt/" title="Python 命令行之旅：深入 docopt">Python 命令行之旅：深入 docopt</a></li><li><a href="/2019/10/30/Python-命令行之旅：使用-docopt-实现-git-命令/" title="Python 命令行之旅：使用 docopt 实现 git 命令">Python 命令行之旅：使用 docopt 实现 git 命令</a></li><li><a href="/2019/11/04/Python-命令行之旅：初探-click/" title="Python 命令行之旅：初探 click">Python 命令行之旅：初探 click</a></li><li><a href="/2019/11/11/Python-命令行之旅：深入-click（一）/" title="Python 命令行之旅：深入 click（一）">Python 命令行之旅：深入 click（一）</a></li><li><a href="/2019/11/17/Python-命令行之旅：深入-click（二）/" title="Python 命令行之旅：深入 click（二）">Python 命令行之旅：深入 click（二）</a></li><li><a href="/2019/11/25/Python-命令行之旅：深入-click（三）/" title="Python 命令行之旅：深入 click（三）">Python 命令行之旅：深入 click（三）</a></li><li><a href="/2019/12/09/Python-命令行之旅：深入-click（四）/" title="Python 命令行之旅：深入 click（四）">Python 命令行之旅：深入 click（四）</a></li><li><a href="/2019/12/14/Python-命令行之旅：使用-click-实现-git-命令/" title="Python 命令行之旅：使用 click 实现 git 命令">Python 命令行之旅：使用 click 实现 git 命令</a></li><li><a href="/2019/12/22/Python-命令行之旅：初探-fire/" title="Python 命令行之旅：初探 fire">Python 命令行之旅：初探 fire</a></li><li><a href="/2019/12/29/Python-命令行之旅：深入-fire（一）/" title="Python 命令行之旅：深入 fire（一）">Python 命令行之旅：深入 fire（一）</a></li><li><a href="/2020/01/05/Python-命令行之旅：深入-fire（二）/" title="Python 命令行之旅：深入 fire（二）">Python 命令行之旅：深入 fire（二）</a></li><li><a href="/2020/01/12/Python-命令行之旅：使用-fire-实现-git-命令/" title="Python 命令行之旅：使用 fire 实现 git 命令">Python 命令行之旅：使用 fire 实现 git 命令</a></li><li><a href="/2020/02/09/Python-命令行之旅：argparse、docopt、click-和-fire-总结篇/" title="Python 命令行之旅：argparse、docopt、click 和 fire 总结篇">Python 命令行之旅：argparse、docopt、click 和 fire 总结篇</a></li><li><a href="/2020/07/02/Python-命令行大乱斗/" title="Python 命令行大乱斗">Python 命令行大乱斗</a></li></ul><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;在前面三篇文章中，我们介绍了 &lt;code&gt;click&lt;/code&gt; 中的参数、选项和命令，本文将介绍 &lt;code&gt;click&lt;/code&gt; 锦上添花的功能，以帮助我们更加轻松地打造一个更加强大的命令行程序。&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;本系列文章默认使用 Python 3 作为解释器进行讲解。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;若你仍在使用 Python 2，请注意两者之间语法和库的使用差异哦~&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/categories/Python/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="命令行" scheme="http://prodesire.cn/tags/%E5%91%BD%E4%BB%A4%E8%A1%8C/"/>
    
      <category term="click" scheme="http://prodesire.cn/tags/click/"/>
    
  </entry>
  
  <entry>
    <title>为终端设置代理</title>
    <link href="http://prodesire.cn/2019/12/08/%E4%B8%BA%E7%BB%88%E7%AB%AF%E8%AE%BE%E7%BD%AE%E4%BB%A3%E7%90%86/"/>
    <id>http://prodesire.cn/2019/12/08/%E4%B8%BA%E7%BB%88%E7%AB%AF%E8%AE%BE%E7%BD%AE%E4%BB%A3%E7%90%86/</id>
    <published>2019-12-08T13:35:04.000Z</published>
    <updated>2020-07-24T15:00:04.372Z</updated>
    
    <content type="html"><![CDATA[<p>有时候通过终端访问 github 等国外网站的速度感人，需要为终端设置代理来提高速度，然而不同平台上的命令我老忘记，遂记录已备忘。</p><p>下文我们假设代理地址是 <code>127.0.0.1:1080</code>。</p><a id="more"></a><h2 id="Linux-Unix"><a href="#Linux-Unix" class="headerlink" title="Linux/Unix"></a>Linux/Unix</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># 设置代理</span><br><span class="line">set http_proxy=http://127.0.0.1:1080</span><br><span class="line">set https_proxy=http://127.0.0.1:1080</span><br><span class="line"></span><br><span class="line"># 查看代理</span><br><span class="line">echo $http_proxy</span><br><span class="line">echo $https_proxy</span><br><span class="line"></span><br><span class="line"># 取消代理</span><br><span class="line">set http_proxy=</span><br><span class="line">set https_proxy=</span><br></pre></td></tr></table></figure><h2 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 设置代理</span><br><span class="line">netsh winhttp set proxy 127.0.0.1:1080</span><br><span class="line"></span><br><span class="line"># 查看代理</span><br><span class="line">netsh winhttp show proxy</span><br><span class="line"></span><br><span class="line"># 取消代理</span><br><span class="line">netsh winhttp reset proxy</span><br></pre></td></tr></table></figure><h2 id="写个脚本工具"><a href="#写个脚本工具" class="headerlink" title="写个脚本工具"></a>写个脚本工具</h2><p>对于使用多个平台的我来说，用到再去翻找命令还是有些麻烦，所以不如写个脚本工具来跨平台使用。</p><p>项目地址： <a href="https://github.com/Prodesire/terminal-proxy" target="_blank" rel="noopener">https://github.com/Prodesire/terminal-proxy</a> 。</p><p>不论是什么平台，用法都非常简单：</p><p>首先是安装：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install terminal-proxy</span><br></pre></td></tr></table></figure><p>然后配置一遍代理地址：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">proxy config 127.0.0.1:1080</span><br></pre></td></tr></table></figure><p>然后就可以愉快地在命令行中开启关闭代理了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 开启代理</span><br><span class="line">proxy on</span><br><span class="line"></span><br><span class="line"># 查看代理</span><br><span class="line">proxy show</span><br><span class="line"></span><br><span class="line"># 关闭代理</span><br><span class="line">proxy off</span><br></pre></td></tr></table></figure><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;有时候通过终端访问 github 等国外网站的速度感人，需要为终端设置代理来提高速度，然而不同平台上的命令我老忘记，遂记录已备忘。&lt;/p&gt;
&lt;p&gt;下文我们假设代理地址是 &lt;code&gt;127.0.0.1:1080&lt;/code&gt;。&lt;/p&gt;
    
    </summary>
    
    
    
  </entry>
  
  <entry>
    <title>Python 壹周刊 003</title>
    <link href="http://prodesire.cn/2019/12/03/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-003/"/>
    <id>http://prodesire.cn/2019/12/03/Python-%E5%A3%B9%E5%91%A8%E5%88%8A-003/</id>
    <published>2019-12-03T12:00:00.000Z</published>
    <updated>2020-07-24T15:00:04.360Z</updated>
    
    <content type="html"><![CDATA[<h2 id="新鲜事儿"><a href="#新鲜事儿" class="headerlink" title="新鲜事儿"></a>新鲜事儿</h2><p><a href="https://docs.python.org/3.9/whatsnew/3.9.html" target="_blank" rel="noopener">Python 3.9a1 都有哪些新内容</a></p><p>Python 3.9a1 发布了，快看看有哪些内容。后续将会专门写篇文章详细介绍。</p><a id="more"></a><h2 id="好文共赏"><a href="#好文共赏" class="headerlink" title="好文共赏"></a>好文共赏</h2><p><a href="https://nedbatchelder.com/text/unipain.html" target="_blank" rel="noopener">实用的 Unicode</a></p><p>2012 年的关于 Unicode 的老文章，但仍非常值得阅读。相信不少同学在 Python 2 上关于字符编码问题踩了不少坑。这篇文章带你透过现象看本质。</p><p><a href="https://blog.dropbox.com/topics/work-culture/-the-mind-at-work--guido-van-rossum-on-how-python-makes-thinking" target="_blank" rel="noopener">工作思路：Guido van Rossum 谈 Python 如何使代码思考变得更轻松</a></p><p>本周早些时候推荐的文章。</p><p><a href="https://dev.to/duomly/10-reasons-why-learning-python-is-still-a-great-idea-5abh" target="_blank" rel="noopener">为什么学习 Python 仍然是个好主意的十个理由</a></p><p>学习 Python 永远都不会晚。</p><p><a href="https://dev.to/coderasha/let-me-introduce-you-reverse-python-1eef" target="_blank" rel="noopener">Reverse Python 介绍</a></p><p>这是一个科学技术研究平台，对此感兴趣的同学可以了解下。</p><p><a href="https://ahmedbesbes.com/end-to-end-ml.html" target="_blank" rel="noopener">使用 Python 从零开始将机器学习应用程序构建和部署到 AWS：端到端教程</a></p><p>关于机器学习全栈技术从零开始的教程，涉及到使用 selenium 和 scrapy 进行爬虫，使用 pandas 进行数据处理，使用 pytorch 进行模型训练，使用 Dash、Falsk 和 PostgreSQL 搭建服务端以及使用 Docker 容器化，并最终部署到 AWS 上。</p><p><a href="https://www.techbeamers.com/top-python-libraries-data-science/" target="_blank" rel="noopener">用于数据科学项目的 Top 25 Python 库</a></p><p>涉及到数据收集、清洗、可视化、模型化、音频、媒体处理、数据库和 Web 等内容。</p><p><a href="https://bhoey.com/blog/extracting-raw-photo-exif-data-with-python/" target="_blank" rel="noopener">使用 Python 提取原始照片的 EXIF 数据</a></p><p><a href="https://orbifold.xyz/raising-exceptions.html" target="_blank" rel="noopener">在 Python 中更好地抛出异常</a></p><p>我们可能在不经意间误用异常，来看看正确使用姿势。</p><p><a href="https://sobolevn.me/2019/10/testing-django-migrations" target="_blank" rel="noopener">测试 Django 的数据迁移</a></p><p><a href="https://changelog.complete.org/archives/10053-the-incredible-disaster-of-python-3" target="_blank" rel="noopener">Python 3 中 byte/str 分离带来的问题</a></p><p>某些情境下，一些 IO 相关库不能正确处理字符问题，来看看此文章避免采坑。</p><p><a href="https://realpython.com/invalid-syntax-python/" target="_blank" rel="noopener">Python 中非法的语法：SyntaxError 的常见原因</a></p><p>提供了 Python 中非法语法的常见示例，并了解如何解决该问题。</p><p><a href="https://medium.com/blueberryx/learn-to-work-with-next-gen-neurotech-data-fnirs-with-this-easy-tutorial-7ce9272ee9fb" target="_blank" rel="noopener">使用 Python 检测人脑中的“思维强度”</a></p><p>如何开始使用 fNIRS 感测数据，尤其是氧化血红蛋白“ HbO2 / HbO”数据，分析来自传感器的数据流。</p><p><a href="https://plumberjack.blogspot.com/2019/11/a-qt-gui-for-logging.html" target="_blank" rel="noopener">Qt GUI 如何打日志</a></p><p><a href="https://alexandrugris.github.io/maths/2017/04/30/symbolic-maths-python.html" target="_blank" rel="noopener">Python 中的符号数学</a></p><p>执行符号计算的能力是任何面向数学的软件包的关键组成部分。，文章介绍了如何使用 sympy 来实现符号计算。</p><h2 id="赞视频"><a href="#赞视频" class="headerlink" title="赞视频"></a>赞视频</h2><p><a href="https://realpython.com/courses/python-keyerror/" target="_blank" rel="noopener">Python KeyError 异常及处理</a></p><p>KeyError 意味着什么？什么时候抛出这个异常？如何处理它？这个视频教程给你答案。</p><h2 id="酷开源"><a href="#酷开源" class="headerlink" title="酷开源"></a>酷开源</h2><p><a href="https://github.com/ycm-core/YouCompleteMe" target="_blank" rel="noopener">YouCompleteMe</a></p><p>vim 的代码补全引擎。</p><p><a href="https://github.com/quantopian/pyfolio" target="_blank" rel="noopener">pyfolio</a></p><p>Python 的投资组合和风险分析库。</p><p><a href="https://github.com/spotify/luigi" target="_blank" rel="noopener">luigi</a></p><p>用来帮助构建批处理作业复杂管道的 Python 库。</p><p><a href="https://github.com/pyca/cryptography" target="_blank" rel="noopener">cryptography</a></p><p>一个旨在向 Python 开发人员公开密码基元和用法的软件包。</p><p><a href="https://github.com/nltk/nltk" target="_blank" rel="noopener">nltk</a></p><p>NLTK（自然语言工具包）是一套支持自然语言处理研究和开发的开源 Python 模块、数据集和教程。</p><p><a href="https://github.com/maxhumber/gazpacho" target="_blank" rel="noopener">gazpacho</a></p><p>Web 爬虫库。</p><p><a href="https://github.com/pallets/werkzeug" target="_blank" rel="noopener">werkzeug</a></p><p>一个全面的 WSGI Web 应用程序库。Flask 就是基于此库编写的。</p><p><a href="https://github.com/dw/mitogen" target="_blank" rel="noopener">Mitogen</a></p><p>用于编写分布式自复制程序的 Python 库。</p><p><a href="https://github.com/greenbone/autohooks" target="_blank" rel="noopener">autohooks</a></p><p>使用 Python 编写 git hook。</p><p><a href="https://mg.pov.lt/objgraph/" target="_blank" rel="noopener">objgraph</a></p><p>objgraph 帮助直观地浏览 Python 对象图的模块。</p><p><a href="https://github.com/fogleman/Minecraft" target="_blank" rel="noopener">Minecraft</a></p><p>用 Python 和 Pyglet 编写的 Minecraft 简单 demo。</p><div align="center"><br><img src="/images/wechatPublicAccount.png" alt><br></div>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;新鲜事儿&quot;&gt;&lt;a href=&quot;#新鲜事儿&quot; class=&quot;headerlink&quot; title=&quot;新鲜事儿&quot;&gt;&lt;/a&gt;新鲜事儿&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://docs.python.org/3.9/whatsnew/3.9.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Python 3.9a1 都有哪些新内容&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Python 3.9a1 发布了，快看看有哪些内容。后续将会专门写篇文章详细介绍。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Python" scheme="http://prodesire.cn/categories/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/categories/Python/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
    
      <category term="Python" scheme="http://prodesire.cn/tags/Python/"/>
    
      <category term="壹周刊" scheme="http://prodesire.cn/tags/%E5%A3%B9%E5%91%A8%E5%88%8A/"/>
    
  </entry>
  
</feed>
