<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>:: Haku&#39;s ::</title>
  
  <subtitle>生命中幻光 不可追逐</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://b.kohaku.cc/"/>
  <updated>2019-12-25T07:45:14.657Z</updated>
  <id>http://b.kohaku.cc/</id>
  
  <author>
    <name>Haku</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>飞花令助手</title>
    <link href="http://b.kohaku.cc/2019/12/25/poetry-qq-bot/"/>
    <id>http://b.kohaku.cc/2019/12/25/poetry-qq-bot/</id>
    <published>2019-12-25T06:15:47.000Z</published>
    <updated>2019-12-25T07:45:14.657Z</updated>
    
    <content type="html"><![CDATA[<p>曾有一个小小的黑色本子，记录了高中时期痴迷的诗词，还夹有已经干枯的叶子和花，仿佛给墨迹添了一圈香气。我把它视若珍宝，无奈终于在一次意外中遗失，一起遗失的还有一叠名为「遗忘」的信纸，至今耿耿于怀。</p><p>近来尤其喜欢苏轼的《临江仙·夜归临皋》，从前最喜欢的是这首词结尾两句：「小舟从此逝，江海寄余生」，现在重读却更为「家童鼻息已雷鸣，敲门都不应，倚杖听江声」句倾倒，真像极了辛帅写的「少年不识愁滋味，爱上层楼。爱上层楼，为赋新词强说愁」，从前哪里懂得「小舟从此逝，江海寄余生」的境界，只是被其中洒脱的仙气感染，觉得读上两句就能像向苏子一样超凡脱俗。</p><p>看过《中国诗词大会》和《中华好诗词》，尤其喜欢大学季恰同学少年专题节目，酣畅淋漓。后来偶然知道除了飞花令，还有单双飞花令，射覆飞花令，接龙各种诗词游戏玩法，和一群达人玩过一段时间后，偶尔会在一些诗句上卡壳，恰好前段时间在网上抓了一些诗词数据，一时兴起就想写一个自动生成双飞花和射覆游戏的小程序，实现了在群里自动飞花的程序。</p><img src="/2019/12/25/poetry-qq-bot/demo.jpg"><h1 id="素材准备"><a href="#素材准备" class="headerlink" title="素材准备"></a>素材准备</h1><p>首先准备好诗词素材，这里我用的是从互联网上抓取的2万余作者信息的50余万篇诗词文赋，囊括了从古至今绝大部分的作者和作品。由于50多万作品中有很大一部分对我们是陌生的，因此我对它进行了筛选，尽量选择大家熟悉的作品，最终选定了14175篇放入素材池。</p><h1 id="接口开发"><a href="#接口开发" class="headerlink" title="接口开发"></a>接口开发</h1><p>使用Springboot开发Restful接口，通过docker部署到云服务器，接口调用方式如下：<br><code>http://tc.hakucc.com/api/shuangfei?clue=萧瑟秋风今又是 换了人间</code><br><code>http://tc.hakucc.com/api/shefu?clue=月明人倚楼 云7</code></p><h1 id="自动回复"><a href="#自动回复" class="headerlink" title="自动回复"></a>自动回复</h1><p>由于QQBot所依赖的SmartQQ协议停用，最后选择酷Q作为自动回复的工具。酷Q使用教程<a href="酷Q SDK-X">点此</a>，配置完成后，编写<code>CQPlusHandler.py</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="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cqplus</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> random</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">MainHandler</span><span class="params">(cqplus.CQPlusHandler)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">handle_event</span><span class="params">(self, event, params)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> event == <span class="string">'on_private_msg'</span>:</span><br><span class="line">            self.OnEvent_PrivateMsg(params)</span><br><span class="line">        <span class="keyword">elif</span> event == <span class="string">'on_group_msg'</span>:</span><br><span class="line">            self.OnEvent_GroupMsg(params)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">OnEvent_PrivateMsg</span><span class="params">(self, params)</span>:</span>        </span><br><span class="line">        msg = params[<span class="string">'msg'</span>]</span><br><span class="line">        self.api.send_private_msg(params[<span class="string">'from_qq'</span>],msg)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">OnEvent_GroupMsg</span><span class="params">(self,params)</span>:</span></span><br><span class="line">        <span class="comment"># 只针对特定的群</span></span><br><span class="line">        <span class="keyword">if</span> params[<span class="string">'from_group'</span>] <span class="keyword">in</span> [<span class="number">642540069</span>, <span class="number">790583076</span>]:</span><br><span class="line">            result = self.shuangfei(params[<span class="string">'msg'</span>])</span><br><span class="line">            <span class="keyword">if</span> result != <span class="string">''</span>:</span><br><span class="line">                self.api.send_group_msg(params[<span class="string">'from_group'</span>], result)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">shuangfei</span><span class="params">(self, clue)</span>:</span></span><br><span class="line">        url = <span class="string">r'http://tc.hakucc.com/api/shuangfei?clue='</span> + clue</span><br><span class="line">        r = requests.get(url)</span><br><span class="line">        <span class="keyword">if</span> r.json()[<span class="string">'code'</span>] == <span class="string">'0000'</span>:</span><br><span class="line">            <span class="keyword">return</span> r.json()[<span class="string">'data'</span>][random.randint(<span class="number">0</span>, <span class="number">20</span>)]</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">''</span></span><br></pre></td></tr></table></figure><p>完成后无需执行<code>CQPlusHandler.py</code>，在酷Q中启用该应用即可。</p><p>参考<br><a href="https://gitee.com/muxiaofei/coolq_sdk_x/wikis/pages" target="_blank" rel="noopener">酷Q SDK-X</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;曾有一个小小的黑色本子，记录了高中时期痴迷的诗词，还夹有已经干枯的叶子和花，仿佛给墨迹添了一圈香气。我把它视若珍宝，无奈终于在一次意外中遗失，一起遗失的还有一叠名为「遗忘」的信纸，至今耿耿于怀。&lt;/p&gt;
&lt;p&gt;近来尤其喜欢苏轼的《临江仙·夜归临皋》，从前最喜欢的是这首词结尾
      
    
    </summary>
    
      <category term="诗词" scheme="http://b.kohaku.cc/categories/%E8%AF%97%E8%AF%8D/"/>
    
      <category term="飞花令" scheme="http://b.kohaku.cc/categories/%E8%AF%97%E8%AF%8D/%E9%A3%9E%E8%8A%B1%E4%BB%A4/"/>
    
    
      <category term="诗词" scheme="http://b.kohaku.cc/tags/%E8%AF%97%E8%AF%8D/"/>
    
  </entry>
  
  <entry>
    <title>如何解决跨域请求问题</title>
    <link href="http://b.kohaku.cc/2018/12/22/cross-origin-request/"/>
    <id>http://b.kohaku.cc/2018/12/22/cross-origin-request/</id>
    <published>2018-12-22T11:16:53.000Z</published>
    <updated>2019-12-25T03:38:27.101Z</updated>
    
    <content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>同源策略是<strong>浏览器端</strong>web应用安全的一个重要概念，在这个策略下，浏览器禁止页面中的脚本向其他域发送请求（如Ajax），以防止恶意站点读取其他站点的敏感信息；</p><h2 id="场景分析"><a href="#场景分析" class="headerlink" title="场景分析"></a>场景分析</h2><ul><li>假设有一个Web API地址为<code>http://localhost:9000/api/values/</code><br>该API返回一段简单的json数据，如下：<img src="/2018/12/22/cross-origin-request/api-response.png"></li><li>有一个Web页面地址为<code>http://localhost:8080</code><br>页面中仅包含一段JavaScript脚本，请求上述api并在控制台显示返回结果，如果出错则显示错误信息，script内容如下：<figure class="highlight html"><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="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="undefined">    $.ajax(&#123;</span></span><br><span class="line"><span class="undefined">        type: "GET",</span></span><br><span class="line"><span class="undefined">        url: "http://localhost:9000/api/values/",</span></span><br><span class="line"><span class="undefined">    &#125;).done(function (data) &#123;</span></span><br><span class="line"><span class="undefined">        console.log(data);</span></span><br><span class="line"><span class="undefined">    &#125;).error(function (jqXHR, textStatus, errorThrown) &#123;</span></span><br><span class="line"><span class="undefined">        console.log(jqXHR.responseText || textStatus);</span></span><br><span class="line"><span class="undefined">    &#125;);</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure></li></ul><p>当打开页面，发现在控制台出现如下错误：<br><img src="/2018/12/22/cross-origin-request/cors-error-log.png"><br>提示请求被cors策略限制，被请求的资源没有提供<code>Access-Control-Allow-Origin</code>头；出现此错误是由于同源策略的存在。</p><h2 id="何为同源"><a href="#何为同源" class="headerlink" title="何为同源"></a>何为同源</h2><p>只有协议，地址，端口均完全相同的URL才称为同源。<br>以下两个URL同源：</p><ul><li><code>http://example.com/foo.html</code></li><li><code>http://example.com/bar.html</code><br>以下四个URL和上两个URL均不同源：</li><li><code>http://example.net</code> - Different domain</li><li><code>http://example.com:9000/foo.html</code> - Different port</li><li><code>https://example.com/foo.html</code> - Different scheme</li><li><code>http://www.example.com/foo.html</code> - Different subdomain</li></ul><p><code>http://localhost:9000/api/values/</code> 与 <code>http://localhost:8080</code> 不同源，因此从8080端口请求得到的JavaScript脚本中无法向9000端口提供的API发送请求。</p><h2 id="解决跨域请求的几种方法"><a href="#解决跨域请求的几种方法" class="headerlink" title="解决跨域请求的几种方法"></a>解决跨域请求的几种方法</h2><h3 id="JSONP"><a href="#JSONP" class="headerlink" title="JSONP"></a>JSONP</h3><p>在介绍JSONP之前，首先明确一点，即html可以通过script标签从不同的域获取JavaScript脚本，体现为在一个html页面中，可以从abc.com引入script1.js，同时从xyz.com引入script2.js，如下：<br><figure class="highlight html"><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="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"http://abc.com/jquery.min.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"http://xyz.com/bootstrap.min.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure></p><p>实际上，对于支持src属性的标签，都是可以从不同的域引入资源的，比如通过img标签从不同的域获取图片资源等。在此前提下，是否可以将json数据放在JavaScript脚本中被获取呢？考虑一个特定场景，假设html中仅包含一段JavaScript脚本，如下所示，脚本中定义并调用了一个方法mycallback：<br><figure class="highlight html"><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="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span>JSONP<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="undefined">let mycallback = function(data)&#123;</span></span><br><span class="line"><span class="undefined">console.log(data);</span></span><br><span class="line"><span class="undefined">&#125;;</span></span><br><span class="line"><span class="undefined"></span></span><br><span class="line"><span class="undefined">mycallback("Hello Haku.");</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure></p><p>当打开页面，会在控制台看到<code>Hello Haku.</code>的输出；<br>当然也可以把调用<code>mycallback</code>的脚本放到单独的data.js文件，调整为如下：<br><figure class="highlight html"><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">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span>JSONP<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="undefined">let mycallback = function(data)&#123;</span></span><br><span class="line"><span class="undefined">console.log(data);</span></span><br><span class="line"><span class="undefined">&#125;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"data.js"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure></p><p>data.js仅包含调用mycallback语句：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mycallback([&#123;<span class="string">"name"</span>: <span class="string">"haku"</span>&#125;, &#123;<span class="string">"name"</span>: <span class="string">"chihiro"</span>&#125;])</span><br></pre></td></tr></table></figure></p><p>当刷新页面，会在控制台看到<code>[{&quot;name&quot;: &quot;haku&quot;}, {&quot;name&quot;: &quot;chihiro&quot;}]</code>的输出；</p><p>JSONP（JSON with padding）是一种通过注入<code>&lt;script&gt;</code>标签的方式请求数据的JavaScript模式，使得可以绕开同源策的略限制共享数据，此处padding实际上是指一个回调函数（如上例中的mycallback）。由于同源策略的存在返回纯JSON数据的service无法跨域共享数据，但是在<code>&lt;script&gt;</code>元素中可以执行从其他源获取的内容；于是可以在页面中增加一个src为所请求url的<code>&lt;script&gt;</code>元素，JSONP返回的数据不是JSON数据，而是一段script，以JSONP响应对象作为参数的回调函数，这就是为什么JSONP请求中会包含一个callback参数。若需要服务端返回一段JavaScript脚本而不是json格式数据，需要修改服务端代码，以C#为例，为了方便可以直接引入第三方库<code>WebApiContrib.Formatting.Jsonp</code>，在api的配置函数中定义Fomatter，返回一段被回调函数包裹的脚本，代码如下<br><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">var jsonpFormatter = new JsonpMediaTypeFormatter(config.Formatters.JsonFormatter);</span><br><span class="line">config.Formatters.Insert(0, jsonpFormatter);</span><br></pre></td></tr></table></figure></p><p>然后将前端页面修改为：<br><figure class="highlight html"><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">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span>JSONP<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="keyword">let</span> mycallback = <span class="function"><span class="keyword">function</span>(<span class="params">data</span>)</span>&#123;</span></span><br><span class="line"><span class="javascript"><span class="built_in">console</span>.log(data);</span></span><br><span class="line"><span class="undefined">&#125;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"http://localhost:9000/api/values?callback=mycallback"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure></p><p>此时刷新页面，会在控制台看到<code>[{&quot;name&quot;:&quot;abc&quot;,&quot;value&quot;:&quot;cba&quot;},{&quot;name&quot;:&quot;xyz&quot;,&quot;value&quot;:&quot;zyx&quot;}]</code>的输出，并且可以发现返回头中显示的返回类型为JavaScript而不是json，返回的内容如下所示：<br><img src="/2018/12/22/cross-origin-request/response_js.png"></p><p>如果引入了JQuery，则不必手动创建<code>script</code>标签，jQuery会自动创建和插入，只需要在ajax请求中将<code>dataType</code>属性设置为<code>jsonp</code> ，将前端页面调整为如下所示，即我们常见的jsonp使用方法：<br><figure class="highlight html"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span>JSONP<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="undefined">        </span></span><br><span class="line"><span class="javascript">        $.ajax(&#123;</span></span><br><span class="line"><span class="javascript">            type: <span class="string">"GET"</span>,</span></span><br><span class="line"><span class="javascript">            url: <span class="string">"http://localhost:9000/api/values"</span>,</span></span><br><span class="line"><span class="javascript">            dataType:<span class="string">"jsonp"</span>,</span></span><br><span class="line"><span class="javascript">        &#125;).done(<span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span></span><br><span class="line"><span class="javascript">            <span class="built_in">console</span>.log(data);</span></span><br><span class="line"><span class="javascript">        &#125;).error(<span class="function"><span class="keyword">function</span> (<span class="params">jqXHR, textStatus, errorThrown</span>) </span>&#123;</span></span><br><span class="line"><span class="javascript">            <span class="built_in">console</span>.log(jqXHR.responseText || textStatus);</span></span><br><span class="line"><span class="undefined">        &#125;);        </span></span><br><span class="line"><span class="undefined">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure></p><p>此时刷新页面，会在控制台看到<code>[{&quot;name&quot;:&quot;abc&quot;,&quot;value&quot;:&quot;cba&quot;},{&quot;name&quot;:&quot;xyz&quot;,&quot;value&quot;:&quot;zyx&quot;}]</code>的输出。</p><h3 id="CORS"><a href="#CORS" class="headerlink" title="CORS"></a>CORS</h3><p>Cross Origin Request Sharing(CORS) 是W3C标准，允许<strong>服务器</strong>端放松同源策略；通过CORS服务器可以显式地允许一些跨域请求，体现为在response header增加<code>Access-Control-Allow-Origin</code>等属性。CORS在服务器端设置，浏览器端不需要其他修改。<br>以ASP.NET WebAPI为例，引入<code>Microsoft.AspNet.WebApi.Cors</code>库；在VS 中<code>Ctrl + Q</code> 快速启动搜索cors，在NuGet窗口中安装最新版本<code>Microsoft.AspNet.WebApi.Cors</code>；或者在Package Manager Console窗口通过执行命令：<br><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">Install-Package Microsoft.AspNet.WebApi.Cors</span><br></pre></td></tr></table></figure><br>此命令安装最新版本的包并更新依赖，包括<code>System.Web.Cors</code> 和 <code>System.Web.Http.Cors</code>；</p><p>在Web API的配置函数中，增加<code>config.EnableCors();</code>以启用CORS，并在Controller类或者方法上增加<code>[EnableCors]</code>特性，如下：<br><figure class="highlight csharp"><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"><span class="keyword">using</span> System.Web.Http;</span><br><span class="line"><span class="keyword">using</span> System.Web.Http.Cors;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">APIs</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//[EnableCors(origins: "http://localhost:8080", headers: "*", methods: "*")]</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">ValuesController</span> : <span class="title">ApiController</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// GET api/values</span></span><br><span class="line">        [<span class="meta">EnableCors(origins: <span class="meta-string">"http://localhost:8080"</span>, headers: <span class="meta-string">"*"</span>, methods: <span class="meta-string">"*"</span>)</span>]</span><br><span class="line">        <span class="function"><span class="keyword">public</span> IEnumerable&lt;<span class="keyword">object</span>&gt; <span class="title">Get</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function">                </span>&#123;</span><br><span class="line">                    <span class="keyword">var</span> resultList = <span class="keyword">new</span> List&lt;<span class="keyword">object</span>&gt;();</span><br><span class="line">                    resultList.Add(<span class="keyword">new</span> &#123; name = <span class="string">"abc"</span>, <span class="keyword">value</span> = <span class="string">"cba"</span> &#125;);</span><br><span class="line">                    resultList.Add(<span class="keyword">new</span> &#123; name = <span class="string">"xyz"</span>, <span class="keyword">value</span> = <span class="string">"zyx"</span> &#125;);</span><br><span class="line">                    <span class="keyword">return</span> resultList;</span><br><span class="line">                &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>前端页面的ajax请求中一处dataType，调整回：<br><figure class="highlight html"><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"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span>JSONP<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="undefined">        </span></span><br><span class="line"><span class="javascript">        $.ajax(&#123;</span></span><br><span class="line"><span class="javascript">            type: <span class="string">"GET"</span>,</span></span><br><span class="line"><span class="javascript">            url: <span class="string">"http://localhost:9000/api/values"</span>,</span></span><br><span class="line"><span class="javascript">        &#125;).done(<span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span></span><br><span class="line"><span class="javascript">            <span class="built_in">console</span>.log(data);</span></span><br><span class="line"><span class="javascript">        &#125;).error(<span class="function"><span class="keyword">function</span> (<span class="params">jqXHR, textStatus, errorThrown</span>) </span>&#123;</span></span><br><span class="line"><span class="javascript">            <span class="built_in">console</span>.log(jqXHR.responseText || textStatus);</span></span><br><span class="line"><span class="undefined">        &#125;);        </span></span><br><span class="line"><span class="undefined">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure></p><p>此时刷新页面，会在控制台看到<code>[{&quot;name&quot;:&quot;abc&quot;,&quot;value&quot;:&quot;cba&quot;},{&quot;name&quot;:&quot;xyz&quot;,&quot;value&quot;:&quot;zyx&quot;}]</code>的输出，并且响应头中显示返回类型为json，以及<code>Access-Control-Allow-Origin: http://localhost:8080</code> ，如下图所示：<br><img src="/2018/12/22/cross-origin-request/response-header.png"></p><p>CORS 支持在Action级别，Controller级别以及全局级别设置；如需在全局应用CORS策略，只需向<code>EnableCors</code>方法传递一个<code>EnableCorsAttribute</code>实例，如下：<br><figure class="highlight csharp"><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">var</span> cors = <span class="keyword">new</span> EnableCorsAttribute(<span class="string">"http://localhost:8080"</span>, <span class="string">"*"</span>, <span class="string">"*"</span>);</span><br><span class="line">config.EnableCors();</span><br></pre></td></tr></table></figure></p><h3 id="请求转发"><a href="#请求转发" class="headerlink" title="请求转发"></a>请求转发</h3><p>正如前文介绍，同源策略是<strong>浏览器</strong>安全策略，服务端与服务端的交互不受同源策略限制；<br>在无法控制请求资源所在的远端服务器，不能设置返回头<code>Access-Control-Allow-Origin</code>的情况下，可以通过请求转发间接获取资源，即在前端页面发送请求至同源的后端地址，由同源后端与目标服务器交互获取资源，再将数据返回给浏览器端；前端页面所在的web应用中新增一个接口，如下：<br><figure class="highlight java"><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="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(value = <span class="string">"/forward"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ForwardController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(value = <span class="string">"/getvalues"</span>, produces = <span class="string">"application/json"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getValues</span><span class="params">()</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> String uri = <span class="string">"http://localhost:9000/api/values/"</span>;</span><br><span class="line"></span><br><span class="line">        RestTemplate restTemplate = <span class="keyword">new</span> RestTemplate();</span><br><span class="line">        <span class="keyword">return</span> restTemplate.getForObject(uri, String.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>WebAPI应用一处JSONP和CORS配置，将前端页面调整如下，发送请求至同源的<code>http://localhost:8080/forward/getvalues/</code>，由<code>getValues()</code>方法在服务器之间获取数据：<br><figure class="highlight javascript"><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">$.ajax(&#123;</span><br><span class="line">    type: <span class="string">"GET"</span>,</span><br><span class="line">    url: http:<span class="comment">//localhost:8080/forward/getvalues/,</span></span><br><span class="line">&#125;).done(<span class="function"><span class="keyword">function</span> (<span class="params">data</span>) </span>&#123;</span><br><span class="line">    alert(data);</span><br><span class="line">&#125;).error(<span class="function"><span class="keyword">function</span> (<span class="params">jqXHR, textStatus, errorThrown</span>) </span>&#123;</span><br><span class="line">    alert(jqXHR.responseText || textStatus);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p><p>此时刷新页面，会在控制台看到<code>[{&quot;name&quot;:&quot;abc&quot;,&quot;value&quot;:&quot;cba&quot;},{&quot;name&quot;:&quot;xyz&quot;,&quot;value&quot;:&quot;zyx&quot;}]</code>的输出。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>JSONP方式的本质是通过下载JavaScript脚本的方式获取数据，因此只支持HTTP GET方法，CORS更灵活也更安全，应尽量选择使用CORS而避免JSONP。</p><p>参考资料：<br><a href="https://en.wikipedia.org/wiki/JSONP" target="_blank" rel="noopener">JSONP</a><br><a href="https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api" target="_blank" rel="noopener">Enable cross-origin requests</a></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;同源策略是&lt;strong&gt;浏览器端&lt;/strong&gt;web应用安全的一个重要概念，在这个策略下，浏览器禁止页面中的脚本向其他域发送请求（如A
      
    
    </summary>
    
      <category term="programming" scheme="http://b.kohaku.cc/categories/programming/"/>
    
    
      <category term="cors" scheme="http://b.kohaku.cc/tags/cors/"/>
    
  </entry>
  
  <entry>
    <title>最大/最小堆</title>
    <link href="http://b.kohaku.cc/2018/10/19/max-heap/"/>
    <id>http://b.kohaku.cc/2018/10/19/max-heap/</id>
    <published>2018-10-19T06:39:03.000Z</published>
    <updated>2018-10-22T03:43:27.631Z</updated>
    
    <content type="html"><![CDATA[<h1 id="堆的特性"><a href="#堆的特性" class="headerlink" title="堆的特性"></a>堆的特性</h1><ul><li>堆是一棵完全二叉树（除了最底层，其余各层节点数都达到最大，最底层的节点都在左边）；</li><li>堆的左右子树仍然是堆；</li><li>堆中每个节点的值都不大于/不小于其子节点的值；</li></ul><p>由于堆是完全二叉树，所以对于序号<code>i</code>的节点（根节点序号为0）, 其左子节点序号为<code>2i + 1</code>，右子节点序号为<code>2i + 2</code>， 父节点序号为<code>floor((i-1)/2)</code>；</p><h1 id="堆的操作"><a href="#堆的操作" class="headerlink" title="堆的操作"></a>堆的操作</h1><h2 id="调整节点"><a href="#调整节点" class="headerlink" title="调整节点"></a>调整节点</h2><p>找到节点和其子节点中最大的那个，将它与该节点交换，如果节点本身就是最大则不发生交换；一次调整之后，子树不一定仍满足最大堆特性，因此需要继续调整，使整棵树保持最大堆特性。<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><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><br><span class="line">private static void heapifyMax(int[] array, int index, int size)</span><br><span class="line">&#123;</span><br><span class="line">    int left = <span class="number">2</span> * index + <span class="number">1</span>;</span><br><span class="line">    int right = <span class="number">2</span> * index + <span class="number">2</span>;</span><br><span class="line">    int max = index;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(left &lt; size &amp;&amp; array[left] &gt; array[max])</span><br><span class="line">    &#123;</span><br><span class="line">        max = left;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(right &lt; size &amp;&amp; array[right] &gt; array[max])</span><br><span class="line">    &#123;</span><br><span class="line">        max = right;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 有调整，将大的节点上移</span><br><span class="line">    <span class="keyword">if</span>(max != index)</span><br><span class="line">    &#123;</span><br><span class="line">        swap(ref array[index], ref array[max]);</span><br><span class="line">        heapifyMax(array, max, size);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">private static void swap(ref int a, ref int b)</span><br><span class="line">&#123;</span><br><span class="line">    int temp = a;</span><br><span class="line">    a = b;</span><br><span class="line">    b = temp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="构造最大堆"><a href="#构造最大堆" class="headerlink" title="构造最大堆"></a>构造最大堆</h2><p>从最后一个叶子节点的父节点开始调整节点，直到前面所有节点都调整完毕，最大堆即构造完成；<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><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">private static void buildMaxHeap(int[] array)</span><br><span class="line">&#123;</span><br><span class="line">    // 从最后一个节点的父节点开始，向上调用调整最大堆</span><br><span class="line">    <span class="keyword">for</span>(int i = array.Length /<span class="number">2</span> <span class="number">-1</span>; i &gt;= <span class="number">0</span>; i--)</span><br><span class="line">    &#123;</span><br><span class="line">        heapifyMax(array, i, array.Length);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="插入节点"><a href="#插入节点" class="headerlink" title="插入节点"></a>插入节点</h2><p>将节点插入到二叉树的最后一个叶子结点位置上，然后将它与它的父节点比较，如果比父节点小则停下，如果大则交换位置，然后对父节点递归直到根节点；</p><h1 id="堆排序"><a href="#堆排序" class="headerlink" title="堆排序"></a>堆排序</h1><p>最大堆中，堆顶元素的值最大，取出堆顶元素后调整堆中余下节点，使继续保持最大堆的特性，再次取出堆顶元素，持续此过程直到最后一个元素。<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><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">//性；下次操作时排除尾部已排序的元素（通过heapifyMax 方法的最后一个参数决定）</span><br><span class="line">private static void heapSort(int[] array)</span><br><span class="line">&#123;</span><br><span class="line">    buildMaxHeap(array);</span><br><span class="line">    <span class="keyword">for</span>(int i = array.Length - <span class="number">1</span>; i &gt; <span class="number">0</span>; i--)</span><br><span class="line">    &#123;</span><br><span class="line">        swap(ref array[<span class="number">0</span>], ref array[i]);</span><br><span class="line">        heapifyMax(array, <span class="number">0</span>, i);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h1 id="TOP-K"><a href="#TOP-K" class="headerlink" title="TOP K"></a>TOP K</h1><p>从大量数据中找出前K 大的数。最直接的方法对所有数据进行排序后取前K 个。我们的目的是找到这K个数，既不需要管其他的数是何顺序，也不要求这K个数有序，数据量大时对所有数据进行排序性价比太低。一种思路是维护一个大小为K 的最小堆，首先将任意K 个数放入堆中，余下的每个元素依次与堆顶元素进行比较，如果它比堆顶元素大，则删除堆顶元素，并将新的元素放入堆中，重新调整堆；<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><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">// 找到最大的K个元素</span><br><span class="line">private static void topK(int[] array, int K, int[] kMax)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">for</span>(int i = <span class="number">0</span>; i &lt; K; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        kMax[i] = array[i];</span><br><span class="line">    &#125;</span><br><span class="line">    buildMinHeap(kMax);</span><br><span class="line">    <span class="keyword">for</span> (int j = K; j &lt; array.Length; j++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span>(array[j] &gt; kMax[<span class="number">0</span>])</span><br><span class="line">        &#123;</span><br><span class="line">            swap(ref kMax[<span class="number">0</span>], ref array[j]);</span><br><span class="line">            heapifyMin(kMax, <span class="number">0</span>, K);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;堆的特性&quot;&gt;&lt;a href=&quot;#堆的特性&quot; class=&quot;headerlink&quot; title=&quot;堆的特性&quot;&gt;&lt;/a&gt;堆的特性&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;堆是一棵完全二叉树（除了最底层，其余各层节点数都达到最大，最底层的节点都在左边）；&lt;/li&gt;
&lt;li&gt;堆的左右子
      
    
    </summary>
    
    
      <category term="algorithm" scheme="http://b.kohaku.cc/tags/algorithm/"/>
    
  </entry>
  
  <entry>
    <title>光里的妈妈</title>
    <link href="http://b.kohaku.cc/2018/10/16/mom-in-the-light/"/>
    <id>http://b.kohaku.cc/2018/10/16/mom-in-the-light/</id>
    <published>2018-10-16T13:36:16.000Z</published>
    <updated>2018-10-19T00:27:42.052Z</updated>
    
    <content type="html"><![CDATA[<p>十月的天已经黑得很早，下班回家时天已经黑了八九分。</p><p>每次路过窗户的时候，我总喜欢停下来，对窗里的妈妈呼喊几声。以前天黑得晚，看到不进门在外张牙舞爪的我，她会习惯性的嗔骂一句，我就十分开心。</p><p>这一次我见厨房亮着灯，妈正用心地准备晚上的饭菜。我努力地蹦跳呼喊都没有被发觉，才意识到她在里头看不见一片漆黑中的我，也听不到我的声音：这扇窗就这样隔绝了我俩。慢慢地周围变得安静起来，时间的概念逐渐模糊，不知道经历了1秒还是10秒还是1分钟，我是旁观者凝望着只有影像没有声音的画面，心里暖暖的……</p><p>按下门铃我飞快回到窗前，她擦了擦手，转身要去开门，专注的脸上生出笑容。我的世界终于恢复如常。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;十月的天已经黑得很早，下班回家时天已经黑了八九分。&lt;/p&gt;
&lt;p&gt;每次路过窗户的时候，我总喜欢停下来，对窗里的妈妈呼喊几声。以前天黑得晚，看到不进门在外张牙舞爪的我，她会习惯性的嗔骂一句，我就十分开心。&lt;/p&gt;
&lt;p&gt;这一次我见厨房亮着灯，妈正用心地准备晚上的饭菜。我努力地
      
    
    </summary>
    
      <category term="随笔" scheme="http://b.kohaku.cc/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="family" scheme="http://b.kohaku.cc/tags/family/"/>
    
  </entry>
  
  <entry>
    <title>指缝</title>
    <link href="http://b.kohaku.cc/2018/09/22/oneday/"/>
    <id>http://b.kohaku.cc/2018/09/22/oneday/</id>
    <published>2018-09-22T03:07:07.000Z</published>
    <updated>2018-10-19T00:52:15.707Z</updated>
    
    <content type="html"><![CDATA[<img src="/2018/09/22/oneday/1.jpg"><p>今天是中秋假期的第一天，早晨眼睛睁开得很干脆，像是被一种力量召唤。</p><p>眼角余光察觉到窗帘被风吸出窗外，形成一片饱满的穹幕。轻风穿过薄被，透过皮囊，缓缓波动的穹幕就是我此刻的心情。</p><p>不知道是不是幻觉，房间亮的很清澈。浮动的帘幕遮挡住视线，却解放了想象力：我看到了晨光，听到了树叶的声音，站在了高耸入云的楼顶，在一刹那精骛八极，心游万仞……</p><p>兴冲冲，失落落，直为自己早早拉开窗帘的行为懊恼不已。</p><img src="/2018/09/22/oneday/pic.jpg">]]></content>
    
    <summary type="html">
    
      
      
        &lt;img src=&quot;/2018/09/22/oneday/1.jpg&quot;&gt;
&lt;p&gt;今天是中秋假期的第一天，早晨眼睛睁开得很干脆，像是被一种力量召唤。&lt;/p&gt;
&lt;p&gt;眼角余光察觉到窗帘被风吸出窗外，形成一片饱满的穹幕。轻风穿过薄被，透过皮囊，缓缓波动的穹幕就是我此刻的心情。&lt;/p&gt;

      
    
    </summary>
    
      <category term="随笔" scheme="http://b.kohaku.cc/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
      <category term="随笔" scheme="http://b.kohaku.cc/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>从零开始五子棋</title>
    <link href="http://b.kohaku.cc/2018/08/10/HakuGo/"/>
    <id>http://b.kohaku.cc/2018/08/10/HakuGo/</id>
    <published>2018-08-10T02:31:14.000Z</published>
    <updated>2018-08-10T07:49:22.116Z</updated>
    
    <content type="html"><![CDATA[<p>在学校的时候上王老师的人工智能课，了解博弈树、启发式搜索、剪枝这些基本概念，很遗憾在当时没有完成五子棋。最近在和x 下五子棋的时候突然念头一闪，于是有了这篇文章和基于Minimax 算法的五子棋程序。程序源码：<a href="https://github.com/VincentGau/TicTacToe" target="_blank" rel="noopener">https://github.com/VincentGau/TicTacToe</a> 包含井字棋和五子棋代码；</p><p>五子棋游戏是一个两方博弈过程，双方都能获取局面的完备信息，交替行动，最终一人获胜或达到平局，典型的零和博弈。适合使用极大极小值搜索来求解。极大极小值搜索是实现这个五子棋程序的基础。</p><h1 id="极大极小值搜索"><a href="#极大极小值搜索" class="headerlink" title="极大极小值搜索"></a>极大极小值搜索</h1><p>极大极小值搜索过程的本质是回溯，以深度优先方式遍历多叉树。<br>以棋类游戏为例，对于一个局面，一方有多种可能的落子法，对于它的任何一种落子情况，对方也都有多种落子，如此轮流行动。以多叉树形式表示，树的每一个节点表示一种可能的盘面，子节点表示所有可行方案，树的每一层代表一方，双方在树中交替出现。为了获取最后的胜利，一方要在所有可选项中做出能将其优势最大化的决策，另一方则选择令对手优势最小化的方法。极大极小值搜索过程中，<strong>假设每一步对方也会按照它的最佳方案行动</strong>；我们将博弈树中的层分成Max 层和Min 层，为了保证自己的收益最大化，Max 层节点会始终选择值最大的子节点来更新自己的值，Min层则相反，始终选择值最小的子节点；因此这个算法被称为Minimax。 以一个简单的图理解Minimax 算法：</p><img src="/2018/08/10/HakuGo/minimax.png"><p>上图中，有两类节点，分别代表博弈双方，</p><ul><li>方块，Max 层节点，代表我方回合，他始终选择子节点中的最大值来更新自己的值；</li><li>圆圈，Min 层节点，代表对方回合，他始终选择子节点中的最小值来更新自己的值；<br>现在节点1有两种选择，它应该做出怎样的选择，就是Minimax 算法要解决的问题。<br>节点1 处于Max 层，它要选择2、3 中评价值最高的那一个；节点2 处于Min 层，它取值为所有子节点中最小的值，也就是节点5，所以节点2 的值为 3；同理，节点3 的值为1； 节点1 的值为3；所以节点1 应该选择第一种下法；虽然第三层四个节点中，评价值最大的是节点7，但是如果节点1 选择第二种方案，到达节点3，到对方轮次，此时决定权已经到对方手中，为了使利益最大化，对方会选择到节点6；</li></ul><p>搜索的目标是选择一处收益最高的位置落子，上面的例子里，我们认为叶子结点的值是已知的，而实际上对于五子棋游戏，我们还不知道节点的值，为此，我们需要设定一个评判标准给节点打分，这就是后面我们会提到的<code>评价函数</code>。</p><h1 id="评价函数"><a href="#评价函数" class="headerlink" title="评价函数"></a>评价函数</h1><p>评价函数是影响决策的关键。首先了解五子棋的一些特征局面，</p><ul><li>连五：出现连续五个同色棋子；</li><li>活四，有两点可以连五；</li><li>冲四：有一点可以连五；</li><li>活三：可以形成活四的连三；</li><li>眠三：只能形成冲四的连三；</li><li>活二：可以形成活三的连二；</li><li>眠二：只能形成眠三的连二；</li></ul><h2 id="评分规则"><a href="#评分规则" class="headerlink" title="评分规则"></a>评分规则</h2><p>如果盘面中有我方成五，表示我方已经获胜，因此给这个局面一个很高的评分（比如10000分），如果有对方成五，则给一个很低的评分（比如-10000分）；除了成五之外，活四等也是一个有利的局面，只需一步就可获胜，我们也给他一个相对较高的评分，比如8000（对方活四的话就给 -5000分）；类似地给双冲四、冲四活三、单活三等其他情况都设定一个分数：</p><table><thead><tr><th>棋型</th><th>评分</th></tr></thead><tbody><tr><td>成五</td><td>10000</td></tr><tr><td>活四、双冲四、冲四活三</td><td>5000</td></tr><tr><td>双活三</td><td>3000</td></tr><tr><td>活三眠三</td><td>2000</td></tr><tr><td>冲四</td><td>1000</td></tr><tr><td>活三</td><td>500</td></tr><tr><td>双活二</td><td>400</td></tr><tr><td>眠三</td><td>300</td></tr><tr><td>活二</td><td>200</td></tr><tr><td>冲二</td><td>100</td></tr><tr><td>活一</td><td>70</td></tr><tr><td>死一</td><td>50</td></tr></tbody></table><p>我们对盘面上的每一个点进行评分（我方为正，对方为负）；盘面的评价函数定为所有点中最高得分和最低得分之和。</p><h2 id="点的评价函数"><a href="#点的评价函数" class="headerlink" title="点的评价函数"></a>点的评价函数</h2><p>给每一点估值时，向八个方向扩展若干个位置，</p><h1 id="α-β-剪枝"><a href="#α-β-剪枝" class="headerlink" title="α-β 剪枝"></a>α-β 剪枝</h1><h2 id="理解α-β剪枝"><a href="#理解α-β剪枝" class="headerlink" title="理解α-β剪枝"></a>理解α-β剪枝</h2><p>alpha-beta 剪枝是在Minimax 算法基础上的优化，避免扩展哪些不会被选择的节点，减少无效操作从而提高搜索效率。继续使用前面的例子来理解剪枝的过程。</p><img src="/2018/08/10/HakuGo/minimax.png"><p>我们扩展节点2，得到它的值为3；继续扩展节点3，因为节点3 处于Min 层，且节点6 的值为1，所以节点3 的值不可能大于1， 因此不论节点3 后面的子节点的值是多少，节点1 一定会选择到节点2，所以在得知节点6 的值为1 之后，节点3 到节点7 这条分支可以直接忽略，这个过程称为剪枝；</p><p>这里只对剪枝做了大概的理解，更多参见<a href="http://web.cs.ucla.edu/~rosen/161/notes/alphabeta.html" target="_blank" rel="noopener">α-β剪枝</a></p><h2 id="算法实现"><a href="#算法实现" class="headerlink" title="算法实现"></a>算法实现</h2><p>为每一个节点增加α 和β属性，α 表示最大下界， β表示最小上界；在节点扩展过程中持续更新α 和β 的值，在Max 层更新α， Min层更新β；如果发现α 大于β， 则进行剪枝，不再计算后续子节点；</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><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></pre></td><td class="code"><pre><span class="line">public int minimax()</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">       </span><br><span class="line">    // Max layer, computer&apos;s turn </span><br><span class="line">    if (turn == 0)</span><br><span class="line">    &#123;</span><br><span class="line">        int maxInChild = int.MinValue;</span><br><span class="line">        foreach (var move in availableMoves)</span><br><span class="line">        &#123;</span><br><span class="line">            BoardState childState = new BoardState(newBoard(move, Helper.AIMark), changeTurn(turn), depth + 1, alpha, beta);</span><br><span class="line">            </span><br><span class="line">            // 搜索到最大深度或者已分出胜负，直接计算盘面评分</span><br><span class="line">            if (depth &gt;= Helper.searchDepth || checkFinished(childState.board).Count == 5)</span><br><span class="line">            &#123;</span><br><span class="line">                childScore = evaluateBoard(childState.board);</span><br><span class="line">                nextState = childState;</span><br><span class="line">            &#125;</span><br><span class="line">            // 否则，继续计算子节点的评分选取最大者</span><br><span class="line">            else</span><br><span class="line">            &#123;</span><br><span class="line">                childScore = childState.minimax();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">            if (childScore &gt; maxInChild)</span><br><span class="line">            &#123;</span><br><span class="line">                maxInChild = childScore;</span><br><span class="line">                alpha = maxInChild;</span><br><span class="line">                nextState = childState;</span><br><span class="line">                nextMove = move;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            // 剪枝</span><br><span class="line">            if (alpha &gt;= beta)</span><br><span class="line">                break;</span><br><span class="line">        &#125;</span><br><span class="line">        return maxInChild;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    //Min layer, player&apos;s turn</span><br><span class="line">    else if (turn == 1)</span><br><span class="line">    &#123;</span><br><span class="line">        // 与上类似</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return 0;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="算法优化"><a href="#算法优化" class="headerlink" title="算法优化"></a>算法优化</h1><p>了解了Minimax 算法之后，我们可以想象，随着搜索深度的增加，需要计算的节点呈指数增长，虽然相较围棋复杂度低很多，但是我们想要穷举搜索这颗博弈树的所有叶子结点（最终一方获胜或平局）依然是不可行的，因此我们需要对此做一个限制，有两个方向，一个是设定搜索深度，这个深度即AI思索的步数，另一个是缩小可行落子点的范围，对于一些明显不适合的落点我们不进行扩展。</p><h2 id="缩小候选点范围"><a href="#缩小候选点范围" class="headerlink" title="缩小候选点范围"></a>缩小候选点范围</h2><p>五子棋盘有 15 * 15个点，所有没有落子的地方都是可行的，我们不可能对每种可能的落子情况都进行扩展，这些点中有一些明显不符合最优解，比如边角处，远离棋子圈处，我们可以通过缩小候选落子点范围来提升效率。在实践中，我选择那些周围有棋子存在的空位作为可能的落子点，比如棋盘上对方下了第一步，我们只考虑它周围一圈的八个点，因为只有这八个点周围有棋子（棋盘上的唯一一颗棋子），这样极大第减少了需要扩展的点，这样选取候选点没有经过科学验证，虽然可能失去潜在的最佳方案，但是直觉上落子在远离其他棋子的地方不会获得太多收益；</p><h2 id="候选点排序"><a href="#候选点排序" class="headerlink" title="候选点排序"></a>候选点排序</h2><p>剪枝算法的效率依赖于着法的寻找顺序。如果总是先去搜索最坏的着法，那么剪枝就不会发生，最终会找遍整个博弈树，这时该算法就如同极大极小算法一样，效率非常低。所以我们考虑对候选点进行排序来提升效率。这里涉及到对候选点进行打分，如果在一个候选点落子后可以形成连五（或者阻止对方成五），那么它的评分应该偏高。为此我们首先为各种棋型设定一个评分规则：<br>成五<br>活四、双冲四、冲四活三<br>双活三<br>活三眠三<br>冲四<br>活三<br>双活二<br>眠三<br>活二<br>冲二<br>活一<br>死一</p><p>五子棋盘有15 * 15 个落子点，这些点有一些明显不符合最优解，比如边角处，远离棋子圈处，缩小候选落子点范围能有效提升处理速度和效率；通常棋子圈附近是落子的好选择；刚开局时落子；<br>候选落子位置根据已落子点周围一圈时确定，搜索深度为四层时，时间过长，只计算到达第四层的盘面估值和已经分出胜负的盘面估值；即使一步即可获胜，也需要等到前面的节点完成深度搜索；可以尝试对第一层进行估值，使用最大堆保存估值较高的K 个节点进行深度搜索，可更快发现一步获胜局面；</p><p>棋型：<br>连五：出现连续五个同色棋子；<br>活四，有两点可以连五；<br>冲四：有一点可以连五；<br>活三：可以形成活四的连三；<br>眠三：只能形成冲四的连三；<br>活二：可以形成活三的连二；<br>眠二：只能形成眠三的连二；</p><p>评价函数是影响决策的关键：<br>是对整个盘面进行评估还是对单个落子点进行评估？ 评估是否应该与轮次挂钩？  考虑一种情况：黑子（max）形成冲四，如果是黑子轮，黑子下在第五个延伸位置即可获得连五的分数，如果是白子轮，它下在第五个延伸位置对自己成五可能并没有收益，但是它还是应该下在这个位置防止对方获胜；评价函数用二者之差来表示，博弈树中黑子作为max层追求最大的估值，白子作为min层追求最小的估值，黑子落第五个位置可获得10000分，假设白子落此位置可获得 -10 分（白子连五获得-10000分），</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;在学校的时候上王老师的人工智能课，了解博弈树、启发式搜索、剪枝这些基本概念，很遗憾在当时没有完成五子棋。最近在和x 下五子棋的时候突然念头一闪，于是有了这篇文章和基于Minimax 算法的五子棋程序。程序源码：&lt;a href=&quot;https://github.com/Vinc
      
    
    </summary>
    
    
      <category term="alpha-bate pruning" scheme="http://b.kohaku.cc/tags/alpha-bate-pruning/"/>
    
      <category term="AI" scheme="http://b.kohaku.cc/tags/AI/"/>
    
      <category term="五子棋" scheme="http://b.kohaku.cc/tags/%E4%BA%94%E5%AD%90%E6%A3%8B/"/>
    
  </entry>
  
  <entry>
    <title>ASP.NET Identity用户体系和授权认证</title>
    <link href="http://b.kohaku.cc/2018/06/22/identity/"/>
    <id>http://b.kohaku.cc/2018/06/22/identity/</id>
    <published>2018-06-22T01:30:35.000Z</published>
    <updated>2018-11-27T07:37:43.843Z</updated>
    
    <content type="html"><![CDATA[<p>大多数Web 应用都有用户的存在，有用户必然涉及到授权和认证来控制用户行为，除了页面资源以外，访问其他不允许匿名访问的资源（如webapi）也需要经过授权和认证，系统验证了用户身份的合法性之后用户才可以继续访问。这里分两步：授权和认证，认证（Authentication）验证用户的身份；授权（Authorization）决定用户是否有正确的权限获取指定的资源；授权、认证过程可以直接集成到单个应用程序中，对于由众多应用程序形成的生态系统，为其中每一个应用程序都开发自己的用户体系无疑是重复造轮子，而且会随之产生巨大的运维压力，用户信息的新增、撤销和修改需要在多处同时处理，若再有和外部系统对接的需求，则会使事情更加复杂，因此需要将用户授权认证模块从应用程序剥离，作为一个独立的模块供多个系统共用，基于声明（Claim-based）的联合授权认证是一个可行的方案。</p><h1 id="基于声明的授权认证"><a href="#基于声明的授权认证" class="headerlink" title="基于声明的授权认证"></a>基于声明的授权认证</h1><p>基于声明的授权和认证并不是全新的概念，在联合安全模型中，认证和授权过程与应用程序本身分开，认证和授权是另外的单独的Web服务，即安全令牌服务（Security Token Service, STS），STS负责颁发安全令牌；信赖方应用程序（Relying Party，RP）不进行用户认证，不再保存用户名和密码，不关心认证方如何认证，在认证方认证成功之后接收返回的令牌，令牌中包含了信赖方需要的用户名、角色、权限等用来认证用户身份的信息。这种方式避免了针对一个用户需要管理多个副本的情况，密码同步的问题也不复存在，并且能满足单点登录（SSO）的场景，用户在登录一个应用程序之后无需再次进行身份认证即可获得访问另一个应用的权限。在基于声明的应用程序中，用户身份将通过一组声明来表示，一个声明可能是用户名，也可能是邮箱地址、年龄等等。<br>基于声明的联合认证方式具有以下特点：</p><ul><li>将身份验证机制从应用程序和服务中分离出来，实现与应用程序的松耦合；</li><li>使用声明（Claim）代替角色（Role），声明是一种更灵活、更精确的对象，它可以包括角色及其他信息；</li><li>安全令牌服务STS可以仅作为一个功能模块在用户管理系统中实现，并且依赖方应用程序可以很方便地与STS建立关系；</li></ul><h2 id="认证过程"><a href="#认证过程" class="headerlink" title="认证过程"></a>认证过程</h2><p>在基于声明的应用场景中，通常包含三方参与者，分别是：应用程序自身，终端用户和STS。<br><img src="/2018/06/22/identity/wifbasicwebapp.gif"></p><ol><li>用户访问依赖方应用程序，依赖方应用程序发现该未经认证的请求，于是重定向到STS 服务；</li><li>STS 服务需要用户提供凭证以验证身份，认证成功之后STS 会颁发一个令牌给用户；</li><li>带着这个令牌，用户请求会被STS 服务重定向到依赖方应用程序；</li><li>依赖方应用程序提前通过配置信任该STS 和它颁发的令牌，它会取出令牌中的声明信息，实现用户验证，之后会生成一个cookie，以便下次访问；  </li></ol><p>也就是说，在依赖方与STS服务建立起信任关系之后，当用户请求依赖方应用的资源时，依赖方应用程序不关心登录过程，不关心用户角色，只需要把用户重定向到STS，由STS作验证和返回令牌，依赖方应用程序从令牌中获取需要的信息。</p><h2 id="名词解释"><a href="#名词解释" class="headerlink" title="名词解释"></a>名词解释</h2><p><strong>声明</strong>是标识信息描述，如：用户名，部门，角色信息等。身份提供者验证成功后会返回一组声明，信赖方应用程序根据声明进行授权；应用程序接收到的声明越多，对用户的了解就越详细；应用程序并不会去某一个路径查询用户的声明，而是用户发送声明给应用程序，应用程序会检测这些声明，通过Issuer 字段表示知道声明的颁发者，应用程序只会接受受信任的颁发者生成的声明。<br><strong>令牌</strong>是声明传输的载体，是发行机构（STS）签名的一组序列化的声明；用户会将一组声明和请求一起通过POST 方式发送给应用程序，在安全性要求不高的场景中也可以使用不签名的声明；<br><strong>STS</strong>，安全令牌服务，一个负责认证用户并颁发令牌的服务，它把声明打包进令牌，对令牌进行签名和加密；<br><strong>依赖方应用程序</strong>即依赖声明的应用程序，它将用户认证逻辑代理出去给STS，提取STS 颁发的令牌中的声明并将它们用于身份验证；  </p><h2 id="具体场景"><a href="#具体场景" class="headerlink" title="具体场景"></a>具体场景</h2><p>下面以一个具体场景为例，分析访问过程中的具体HTTP请求进一步理解基于声明的认证：<br>场景中涉及两个应用：一个是依赖方应用（例子中地址为 <code>http://localhost:19851/</code>），需要验证用户身份；一个是用户管理应用，用户数据保存在这里，也在这里对用户信息进行增删改查，同时STS也包含在这个系统中（例子中地址为 <code>http://localhost:62398/</code>）；  </p><img src="/2018/06/22/identity/process.png"><ol><li>用户访问依赖方应用；</li><li>如果已有登录凭据则直接进入访问对应资源；如果没有登录凭据，则跳转向STS服务请求安全令牌，并在请求中附带来源信息以便在登录之后重新跳转回去；</li><li>用户被重定向到Identity用户管理的登录页面；</li><li>用户登录成功后跳转回指定页面，生成Token；  </li><li>经过权限验证之后用户被重定向至最初的依赖方页面；</li></ol><p>这个过程中发生的HTTP请求有：</p><ul><li>请求依赖方地址<code>http://localhost:19851/</code>：<br><strong>request</strong>：GET / HTTP/1.1<br><strong>response</strong>：HTTP/1.1 302 Found<br>Location:<br><code>http://localhost:62398/?wa=wsignin1.0&amp;wtrealm=http%3A%2F%2Flocalhost%3A19851%2F&amp;wctx=rm%3D0%26id%3Dpassive%26ru%3D%252F&amp;wct=2018-09-25T06%3A25%3A12Z</code><br><em>请求依赖方地址，被重定向到STS</em>。以上重定向地址的query string中：<br><code>wa</code> 取值<code>wsignin1.0</code>表示登录请求，<code>wsignout1.0</code>表示登出请求；<br><code>wtrealm</code> 表示依赖方应用的URI， STS通过它来决定是否颁发令牌以及给予哪些声明；<br><code>wct</code> 记录请求时间，STS用于校验判断请求是否超时；<br><code>wtreply</code> 可选项，表示依赖方希望被重定向到的地址；</li></ul><ul><li><p>请求STS<code>http://localhost:62398/</code>，被重定向到登录页面：<br><strong>request</strong>：GET<br><code>/?wa=wsignin1.0&amp;wtrealm=http%3A%2F%2Flocalhost%3A19851%2F&amp;wctx=rm%3D0%26id%3Dpassive%26ru%3D%252F&amp;wct=2018-09-25T06%3A25%3A12Z HTTP/1.1</code><br><strong>response</strong>：HTTP/1.1 302 Found<br>Location:<br><code>/login.aspx?ReturnUrl=%2f%3fwa%3dwsignin1.0%26wtrealm%3dhttp%253A%252F%252Flocalhost%253A19851%252F%26wctx%3drm%253D0%2526id%253Dpassive%2526ru%253D%25252F%26wct%3d2018-09-25T06%253A25%253A12Z&amp;wa=wsignin1.0&amp;wtrealm=http%3A%2F%2Flocalhost%3A19851%2F&amp;wctx=rm%3D0%26id%3Dpassive%26ru%3D%252F&amp;wct=2018-09-25T06%3A25%3A12Z</code><br>由于用户没有登录，被重定向到STS的地址后再次被重定向到登录页面；</p></li><li><p>请求登录页面<code>http://localhost:62398/</code>：<br><strong>request</strong>：GET<br><code>/login.aspx?ReturnUrl=%2f%3fwa%3dwsignin1.0%26wtrealm%3dhttp%253A%252F%252Flocalhost%253A19851%252F%26wctx%3drm%253D0%2526id%253Dpassive%2526ru%253D%25252F%26wct%3d2018-09-25T06%253A25%253A12Z&amp;wa=wsignin1.0&amp;wtrealm=http%3A%2F%2Flocalhost%3A19851%2F&amp;wctx=rm%3D0%26id%3Dpassive%26ru%3D%252F&amp;wct=2018-09-25T06%3A25%3A12Z HTTP/1.1</code><br><strong>response</strong>：HTTP/1.1 200 OK<br>显示STS登录页面，等待用户输入登录信息；</p></li><li><p>用户登录<code>http://localhost:62398/</code>：<br><strong>request</strong>：POST<br><code>/login.aspx?ReturnUrl=%2f%3fwa%3dwsignin1.0%26wtrealm%3dhttp%253A%252F%252Flocalhost%253A19851%252F%26wctx%3drm%253D0%2526id%253Dpassive%2526ru%253D%25252F%26wct%3d2018-09-25T06%253A25%253A12Z&amp;wa=wsignin1.0&amp;wtrealm=http%3a%2f%2flocalhost%3a19851%2f&amp;wctx=rm%3d0%26id%3dpassive%26ru%3d%252F&amp;wct=2018-09-25T06%3a25%3a12Z HTTP/1.1</code></p><img src="/2018/06/22/identity/post-data.png"><p><strong>response</strong>：HTTP/1.1 302 Found<br>Location:<br><code>/?wa=wsignin1.0&amp;wtrealm=http%3A%2F%2Flocalhost%3A19851%2F&amp;wctx=rm%3D0%26id%3Dpassive%26ru%3D%252F&amp;wct=2018-09-25T06%3A25%3A12Z</code><br>Set-Cookie: <code>.ASPXAUTH=blablabla; path=/; HttpOnly</code><br><em>用户POST表单数据之后，被重定向回STS；</em></p></li><li><p>请求STS地址<code>http://localhost:62398/</code>：<br><strong>request</strong>：GET<br><code>/?wa=wsignin1.0&amp;wtrealm=http%3A%2F%2Flocalhost%3A19851%2F&amp;wctx=rm%3D0%26id%3Dpassive%26ru%3D%252F&amp;wct=2018-09-25T06%3A25%3A12Z HTTP/1.1</code><br><strong>response</strong>：HTTP/1.1 200 OK  </p><img src="/2018/06/22/identity/response-body.png"><p>带cookie请求STS地址，响应页面包含一个自动提交的form；</p></li><li><p>自动提交表单<code>http://localhost:19851/</code><br><strong>request</strong>：POST / HTTP/1.1<br>  <code>.ASPXAUTH=blablabla</code><br><strong>response</strong>：HTTP/1.1 302 Found<br>Location: /<br>Set-Cookie: <code>FedAuth=blablabla; path=/; HttpOnly</code><br>Set-Cookie: <code>FedAuth1=blablabla; path=/; HttpOnly</code><br><em>自动提交表单，将token发送到依赖方地址；</em></p></li></ul><ul><li>访问依赖方地址<code>http://localhost:19851/</code>：<br><strong>request</strong>：GET / HTTP/1.1<br><strong>response</strong>：HTTP/1.1 200 OK<br><em>依赖方消费Token，用户最终访问到依赖方应用；</em></li></ul><h2 id="基于声明和基于角色的区别"><a href="#基于声明和基于角色的区别" class="headerlink" title="基于声明和基于角色的区别"></a>基于声明和基于角色的区别</h2><p>在基于角色的访问控制（Role-based Access Control，RBAC）中，用户权限通过一个基于角色的应用程序来管理和执行，如果用户拥有执行一个动作需要的角色，则该动作被允许；和基于角色的模型相比，Claim-based的方式不与角色捆绑，具有更好的灵活性和扩展性，声明并不局限于角色和权限，还能附带用户的其他信息，比如邮箱、生日，比如还可以通过isOver18 声明在不透露用户具体年龄的情况下验证用户是否有权限等等，授权的决定基于声明中的有效数据的任意逻辑，而在RBAC中，唯一使用的声明就是角色。</p><h2 id="Claims认证与OAuth"><a href="#Claims认证与OAuth" class="headerlink" title="Claims认证与OAuth"></a>Claims认证与OAuth</h2><p>OAuth是用于授权的工业标准协议，基于声明的认证与OAuth有不同的适用场景：<br>Claim-based：</p><ul><li>用于应用程序和用户身份认证解耦；</li><li>事前约定建立信任关系，用户声明由身份管理员维护；</li><li>安全令牌服务将声明包含在令牌中颁发并提交给应用；<br>OAuth：</li><li>用户可选授权；</li><li>用于与第三方合作，应用本身有自己的用户体系；</li><li>获取访问令牌去资源服务器获取；</li></ul><h1 id="基于声明的Web应用的实现"><a href="#基于声明的Web应用的实现" class="headerlink" title="基于声明的Web应用的实现"></a>基于声明的Web应用的实现</h1><h2 id="WIF"><a href="#WIF" class="headerlink" title="WIF"></a>WIF</h2><p>Windows Identity Framework（WIF）是一组.Net Framework 类库，实现身份感知的（Identity-aware）、基于声明（Claim-based）的应用程序和服务，WIF 原本作为独立下载的类库发布，现已经集成到.Net 4.5 中。有了WIF，我们可以自定义安全令牌服务，更容易地开发基于声明的Web 应用而无需再安装其他组件。</p><h2 id="依赖方应用配置"><a href="#依赖方应用配置" class="headerlink" title="依赖方应用配置"></a>依赖方应用配置</h2><p>依赖方Web应用程序如需使用已有的自定义STS进行身份认证，需要给项目添加引用并修改配置文件：<br>需要添加的引用包括<code>System.IdentityModel</code> 和<code>System.IdentityModel.Services</code>；<br>需要编辑配置文件web.config，使用基于声明的认证方式：<br><figure class="highlight xml"><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="tag">&lt;<span class="name">system.webServer</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modules</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">add</span> <span class="attr">name</span>=<span class="string">"WSFederationAuthenticationModule"</span> <span class="attr">type</span>=<span class="string">"System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"</span> <span class="attr">preCondition</span>=<span class="string">"managedHandler"</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">add</span> <span class="attr">name</span>=<span class="string">"SessionAuthenticationModule"</span> <span class="attr">type</span>=<span class="string">"System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"</span> <span class="attr">preCondition</span>=<span class="string">"managedHandler"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">modules</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">system.webServer</span>&gt;</span></span><br></pre></td></tr></table></figure></p><p>配置URL地址与处理程序：<br><figure class="highlight xml"><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="tag">&lt;<span class="name">system.identityModel</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">identityConfiguration</span> <span class="attr">saveBootstrapContext</span>=<span class="string">"true"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">issuerTokenResolver</span> <span class="attr">type</span>=<span class="string">"SimpleWebToken.CustomIssuerTokenResolver, SimpleWebToken"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">AddAudienceKeyPair</span>  <span class="attr">symmetricKey</span>=<span class="string">"wAVkldQiFypTQ+kdNdGWCYCHRcee8XmXxOvgmak8vSY="</span> <span class="attr">audience</span>=<span class="string">"http://localhost:19851/"</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">issuerTokenResolver</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">issuerNameRegistry</span> <span class="attr">type</span>=<span class="string">"RelyingParty.TrustedIssuerNameRegistry, RelyingParty"</span>/&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">audienceUris</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">add</span> <span class="attr">value</span>=<span class="string">"http://localhost:19851/"</span>/&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">audienceUris</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">securityTokenHandlers</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">add</span> <span class="attr">type</span>=<span class="string">"SimpleWebToken.SimpleWebTokenHandler, SimpleWebToken"</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">securityTokenHandlers</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">identityConfiguration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">system.identityModel</span>&gt;</span></span><br></pre></td></tr></table></figure></p><figure class="highlight xml"><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="tag">&lt;<span class="name">system.identityModel.services</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">federationConfiguration</span> <span class="attr">identityConfigurationName</span>=<span class="string">""</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">serviceCertificate</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">certificateReference</span> <span class="attr">x509FindType</span>=<span class="string">"FindBySubjectName"</span> <span class="attr">findValue</span>=<span class="string">"localhost"</span> <span class="attr">storeLocation</span>=<span class="string">"LocalMachine"</span> <span class="attr">storeName</span>=<span class="string">"My"</span>/&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">serviceCertificate</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">wsFederation</span> <span class="attr">passiveRedirectEnabled</span>=<span class="string">"true"</span> <span class="attr">issuer</span>=<span class="string">"http://localhost:62398/"</span> <span class="attr">realm</span> =<span class="string">"http://localhost:19851/"</span> <span class="attr">requireHttps</span>=<span class="string">"false"</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">cookieHandler</span> <span class="attr">mode</span>=<span class="string">"Default"</span> <span class="attr">requireSsl</span>=<span class="string">"false"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">chunkedCookieHandler</span> <span class="attr">chunkSize</span>=<span class="string">"2000"</span>/&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">cookieHandler</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">federationConfiguration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">system.identityModel.services</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上述配置中：<br><code>audienceUris</code> 标签指定依赖方应用的URL;<br><code>securityTokenHandlers</code> 标签指定处理token 的类和方法，可以使用自定义的方法或内建的方法；<br><code>issuerNameRegistry</code>标签指定token 处理程序使用的颁发者；<br>配置完成后，访问依赖方应用需要身份认证的页面时会跳转至STS进行登录操作。</p><h1 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h1><p>用户授权认证是大多数应用程序都需要经过的流程，目前各种框架种类众多，基于声明的模型中用户身份由一组声明表示，通过配置一个受信任的外部身份系统为我们自己的应用程序提供关于用户的所有必要信息，在这种模型下，单点登录也能较为简单的实现，应用程序本身不处理任何用户认证相关的逻辑，并且不需要保存用户账户和密码等数据，也不用主动查询用户的详细信息，只需从受信任的令牌发布程序接受安全令牌即可。一个新的应用依赖自定义的STS进行身份认证和授权也只需要简单的配置。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;大多数Web 应用都有用户的存在，有用户必然涉及到授权和认证来控制用户行为，除了页面资源以外，访问其他不允许匿名访问的资源（如webapi）也需要经过授权和认证，系统验证了用户身份的合法性之后用户才可以继续访问。这里分两步：授权和认证，认证（Authentication）验
      
    
    </summary>
    
    
      <category term="asp.net" scheme="http://b.kohaku.cc/tags/asp-net/"/>
    
      <category term="WIF" scheme="http://b.kohaku.cc/tags/WIF/"/>
    
      <category term="identity" scheme="http://b.kohaku.cc/tags/identity/"/>
    
      <category term="authentication" scheme="http://b.kohaku.cc/tags/authentication/"/>
    
      <category term="authorization" scheme="http://b.kohaku.cc/tags/authorization/"/>
    
  </entry>
  
  <entry>
    <title>Jenkins 持续集成</title>
    <link href="http://b.kohaku.cc/2018/04/19/Jenkins-CI/"/>
    <id>http://b.kohaku.cc/2018/04/19/Jenkins-CI/</id>
    <published>2018-04-19T07:05:45.000Z</published>
    <updated>2018-04-19T07:06:07.104Z</updated>
    
    <summary type="html">
    
    </summary>
    
    
      <category term="Jenkins" scheme="http://b.kohaku.cc/tags/Jenkins/"/>
    
      <category term="CI" scheme="http://b.kohaku.cc/tags/CI/"/>
    
  </entry>
  
  <entry>
    <title>Travis 持续集成</title>
    <link href="http://b.kohaku.cc/2018/04/19/Travis-CI/"/>
    <id>http://b.kohaku.cc/2018/04/19/Travis-CI/</id>
    <published>2018-04-19T07:05:04.000Z</published>
    <updated>2018-12-26T01:46:13.717Z</updated>
    
    <content type="html"><![CDATA[<p>使用Travis进行持续集成，会自动配置webhook，在push完成之后执行既定操作。</p><ol><li>直接使用Github账号登录Travis， 勾选需要持续集成的代码库；  </li><li>在项目根目录添加.travis.yml文件：<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">language: python</span><br><span class="line">python:</span><br><span class="line">  - <span class="string">"3.6"</span></span><br><span class="line">install:</span><br><span class="line">  - pip install -r requirements.txt</span><br><span class="line">script:</span><br><span class="line">  - pytest</span><br></pre></td></tr></table></figure></li></ol><p>push代码到代码库之后脚本自动执行，在Travis可以看到执行日志；</p><p>异常情况：</p><ul><li>运行测试出错，提示找不到模块，ModuleNotFoundError: No module named ‘blockchain’<ul><li>在tests目录增加空的<strong>init</strong>.py 文件</li></ul></li><li>运行测试出错，提示找不到文件，FileNotFoundError: [Errno 2] No such file or directory: ‘../src/sites.json’<ul><li>TODO</li></ul></li></ul><blockquote><p>注：TravisCI不支持MSTest，因为MSTest只能在Windows下运行，而目前Travis只在Linux 或者OSX上通过Mono或.Net Core runtime构建C#项目；</p></blockquote><p>在README.md文件增加以下内容，显示构建徽章；<br><code>[![Build Status](https://www.travis-ci.org/VincentGau/pythonScripts.svg?branch=master)](https://www.travis-ci.org/VincentGau/pythonScripts)</code></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;使用Travis进行持续集成，会自动配置webhook，在push完成之后执行既定操作。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;直接使用Github账号登录Travis， 勾选需要持续集成的代码库；  &lt;/li&gt;
&lt;li&gt;在项目根目录添加.travis.yml文件：&lt;figure cl
      
    
    </summary>
    
    
      <category term="CI" scheme="http://b.kohaku.cc/tags/CI/"/>
    
      <category term="Travis" scheme="http://b.kohaku.cc/tags/Travis/"/>
    
      <category term="Github" scheme="http://b.kohaku.cc/tags/Github/"/>
    
  </entry>
  
  <entry>
    <title>写更pythonic 的代码</title>
    <link href="http://b.kohaku.cc/2018/04/16/pythonic-code/"/>
    <id>http://b.kohaku.cc/2018/04/16/pythonic-code/</id>
    <published>2018-04-16T08:43:13.000Z</published>
    <updated>2018-04-27T07:31:01.741Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Python版本"><a href="#Python版本" class="headerlink" title="Python版本"></a>Python版本</h1><p>目前python有两个大版本，Python 2 和Python 3， 这两个版本互不兼容，除了语法上的差异，一些Python 2 的类库没有对应的Python 3 版本，在Python 3中无法使用，Python 3类库也可能不能用在Python 2 中。鉴于python 社区更关注Python 3 的特性和提升，针对Python 2 的更新范围包括bug修复与安全更新，，并且越来越多的开发者企业逐渐放弃对Python 2 的支持，对于新项目Python 3 是一个更长远的选择。不过有虚拟环境的支持，使用不同版本的python也不是问题。<br>安装python的时候默认的解释器是使用C语言开发的CPython，它编译python代码生成字节码然后执行，同样流行的还有JPython（Java），IronPython（.Net），PyPy（python）这些实现，其中CPython的使用更广泛。</p><a id="more"></a><h1 id="遵循PEP8-编码规范"><a href="#遵循PEP8-编码规范" class="headerlink" title="遵循PEP8 编码规范"></a>遵循PEP8 编码规范</h1><p>Python Enhancement Proposal #8（PEP8）是一套python 的编码规范，包括命名规范，缩进使用等，使用统一的编码规范对自己编写代码和方便别人阅读代码都是很有益的。遵循PEP8 来写出更pythonic 的代码。</p><h2 id="命名规范"><a href="#命名规范" class="headerlink" title="命名规范"></a>命名规范</h2><ul><li>函数、变量、属性使用小写字母加下划线格式（eg. my_func）；</li><li>受保护的实例属性以下划线开头（eg. _protected_attr）；</li><li>私有属性以双下划线开头（eg. __private_attr）；</li><li>类和异常使用大写格式（eg. MyClass）；</li></ul><h2 id="空格使用"><a href="#空格使用" class="headerlink" title="空格使用"></a>空格使用</h2><p>经常看到用“游标卡尺”的梗来调侃Python，可见Python对缩进的严格要求，不正确的缩进格式甚至会导致代码无法正确编译，</p><ul><li>缩进使用空格，不用Tab；</li><li>每一层语法上的有效缩进是4个空格；</li><li>每行最多79个字符；</li><li>长表达式在新一行的延续部分额外使用4个空格；</li><li>类中的方法用一个空行隔开；<br>…</li></ul><p>如果使用PyCharm ，通过快捷键Ctrl + Alt + L 迅速格式化代码，默认遵循pep8 规范。</p><h2 id="更Pythonic的表达式和声明"><a href="#更Pythonic的表达式和声明" class="headerlink" title="更Pythonic的表达式和声明"></a>更Pythonic的表达式和声明</h2><ul><li>使用内部否定 <code>if a is not b</code>，而不用肯定表达式的否定 <code>if not a is b</code>；</li><li>使用 <code>if not somelist</code> 来检查是否为空，而不通过检查长度来判定<code>if len(somelist) == 0</code>；</li><li><code>import</code> 语句始终放在文件顶部；</li><li><code>import</code> 模块的时候使用绝对导入，如<code>from foo import bar</code>，而不是只 <code>import foo</code>；如果要做相对导入，也应该使用 <code>from . import foo</code>；</li><li>导入模块的时候按照既定的顺序：先导入标准库模块，然后第三方库，最后导入自己的模块；</li></ul><p>更多规范见 <a href="https://www.python.org/dev/peps/pep-0008/" target="_blank" rel="noopener">PEP8</a></p><p><a href="https://www.pylint.org" target="_blank" rel="noopener">Pylint</a>是一款静态Python 源代码分析工具，检查代码中不合规范的地方，个人认为其作用不止于语法规范，对编码思路也有提升，强烈推荐。</p><p>除了PEP8，Google 也有一套关于python的<a href="http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_language_rules/" target="_blank" rel="noopener">语言和风格规范</a>。<br>建议使用一致的风格。</p><h1 id="Python对象"><a href="#Python对象" class="headerlink" title="Python对象"></a>Python对象</h1><p>python中一切值都是对象；对象由内部数据和各种方法组成；python的对象有三个要素：<code>id</code>， <code>type</code>， <code>value</code>，其中id唯一标示一个对象，<code>type</code>表示对象类型，value是对象的值； <code>a is b</code>，判断的是二者的id是否相同，即二者是否同一对象，==比较二者的值；dir()列出对象所有可用方法；<br>作为动态语言，Python 对象的特征不由它的类型决定，而是通过它的方法决定，称为<em>鸭子类型</em>：</p><blockquote><p>如果看起来像，叫起来像，走起来也像鸭子，那么它就是鸭子  </p></blockquote><h1 id="数据类型-tuple-list-dict-set-namedtuple"><a href="#数据类型-tuple-list-dict-set-namedtuple" class="headerlink" title="数据类型 tuple(,) list[] dict{} set()  namedtuple"></a>数据类型 <code>tuple(,)</code> <code>list[]</code> <code>dict{}</code> <code>set()</code>  <code>namedtuple</code></h1><p><code>tuple</code>（元组） 和<code>list</code>（列表） 非常类似，都是有序的列表，但是<code>tuple</code> 一旦初始化就不能修改，安全性更好；由于系统会为列表分配稍微多一些内存以优化添加新项时的性能， 而元组时不变的，不会占用额外的内存空间；因为小括号既可以表示tuple，也可以表示数学运算中的小括号，所以只有一个元素的<code>tuple</code>定义时需要加一个逗号来消除歧义，如<code>t = (1,)</code>；内置类型<code>list(s)</code>可将任何可迭代类型转换为列表，如果s已经是一个列表，则该函数构造的新列表是s的一个<strong>浅拷贝</strong>；  </p><p><code>set</code>（集合）是无序的，不能通过数字进行索引；集合支持一系列标准操作，如求交集（&amp;），并集（|），差集（-），对称差集（^， 在且仅仅在其中一个集合中）；<code>set</code>中不能存放可变对象，因为无法判断两个可变对象是否相等；创建非空<code>set</code>需要提供一个<code>list</code>作为输入；<br>字典是python中最完善的数据类型；字典在放入一个键值对的时候已经根据key计算出value存放的位置（hash），所以字典的key不能是可变对象（如list），也不能是包含可变对象的对象，必须保证每次计算同一个key的结果相同；要获得字典的关键字列表，只需要将字典转换为列表：<br><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">l = list(d)</span><br></pre></td></tr></table></figure></p><p>使用字典时尽量保持的简洁，不要将其他字典或者元组之类的数据作为字典的值；  </p><p><code>namedtuple</code> 命名元组，当我们打算使用一些轻量的、不可变数据类型，又不想大费周章定义一个真正的class的时候，我们就可以使用<code>namedtuple</code>，支持通过属性而不仅是索引来获取值，体验与class类似，代码如下：<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><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> collections <span class="keyword">import</span> namedtuple</span><br><span class="line">Point = namedtuple(<span class="string">'Point'</span>, (<span class="string">'x'</span>, <span class="string">'y'</span>))</span><br><span class="line"></span><br><span class="line">p = Point(<span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line">print(<span class="string">f'<span class="subst">&#123;p.x&#125;</span> <span class="subst">&#123;p.y&#125;</span>'</span>)</span><br></pre></td></tr></table></figure></p><p><code>namedtuple</code> 使用起来非常方便，但是也有局限性，当属性比较多时，最好还是使用真正的class；</p><h1 id="弄清-str-bytes-unicode"><a href="#弄清-str-bytes-unicode" class="headerlink" title="弄清 str bytes unicode"></a>弄清 <code>str</code> <code>bytes</code> <code>unicode</code></h1><p>在写Python代码的时候，常被编码问题困扰，不胜其烦又无可避免，这一节的目标彻底理解编码。  </p><ul><li>Python 2默认的编码方式是ASCII，而Python 3默认编码格式是Unicode，命名参数<code>encoding</code>在Python 3 中默认都是 <code>utf-8</code>；</li><li>在Python 2 中有两种类型来表示字符序列：<code>str</code> 和 <code>unicode</code>，<code>str</code>实例包含二进制数据，<code>Unicode</code>实例包含Unicode 字符串；Python 3中有两种类型表示字符序列： <code>str</code> 和<code>bytes</code>，<code>str</code>实例包含Unicode字符串，<code>bytes</code>实例包含二进制数据，二者不能通过运算符直接操作；</li><li>使用<code>encode</code>方法将Unicode 字符串转换成二进制数据（编码），反之用<code>decode</code>方法（解码）；  </li></ul><p>错误的出现有时候是因为我们没有搞清楚对象的类型，比如当我们希望操作二进制数据的时候，实际上参数是UTF-8编码的字符；或者当我们在操作Unicode 字符的时候没有指定编码方式，默认ASCII编码，实际上可能是别的编码方式。为了保证输入的数据类型和我们所期望的一致，我们可以通过一个helper 方法，接收<code>str</code> 或者<code>bytes</code> 类型，始终返回<code>str</code>，或者始终返回<code>bytes</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><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="comment"># Python 3</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">to_str</span><span class="params">(bytes_or_str)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> isinstance(bytes_or_str, bytes):</span><br><span class="line">        value = bytes_or_str.decode(<span class="string">'utf-8'</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        value = bytes_or_str</span><br><span class="line">    <span class="keyword">return</span> value <span class="comment"># Instance of str</span></span><br><span class="line">        </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">to_bytes</span><span class="params">(bytes_or_str)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> isinstance(bytes_or_str, str):</span><br><span class="line">        value = bytes_or_str.encode(<span class="string">'utf-8'</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        value = bytes_or_str</span><br><span class="line">    <span class="keyword">return</span> value <span class="comment"># Instance of str</span></span><br></pre></td></tr></table></figure></p><p>还有容易产生混淆的地方是，Python 2里， 如果对象只包含ASCII码， 那么<code>str</code>和<code>unicode</code>看起来没什么区别，他们可以通过 + 运算符进行连接，也能进行比较，也能在格式化字符串<code>&#39;%s&#39;&#39;</code>中使用Unicode 对象，这意味着当形参需要是<code>str</code>时即使传入的参数是<code>unicode</code>（或者反之）程序也能执行；但是在Python 3 中，<code>bytes</code> 和<code>str</code>是完全不同的，即使是空串；</p><p>Python 3里涉及file handles（使用<code>open</code>打开文件）的默认UTF-8编码，Python 2则默认二进制编码，因此</p><h1 id="切片"><a href="#切片" class="headerlink" title="切片"></a>切片</h1><p>切片是Python的一个重要特性，提供简洁的语法方便获取子集，不仅可用于<code>list</code> <code>str</code>等内建数据类型，还可扩展至所有实现了<code>__getitem__</code> <code>__setitem__</code>方法的类。序列类型，包括字符串，列表，元素等，支持解包，索引，切片， <code>all</code>， <code>any</code>等操作  </p><p>切片最基本的使用方式是<code>somelist[start:end]</code>：</p><ul><li>包含start，不包含end（前开后闭 <code>[)</code>）；</li><li>二者都可缺省，可以为负数，表示倒数第几个；</li><li>start 和end 可以超过列表的边界不会报错；</li><li>切片返回的是一个新的列表对象，对它进行修改不会改变原列表；<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">l = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>]</span><br><span class="line">l[:]        <span class="comment"># [1, 2, 3, 4, 5, 6, 7]</span></span><br><span class="line">l[:<span class="number">3</span>]       <span class="comment"># [1, 2, 3]</span></span><br><span class="line">l[<span class="number">2</span>:]       <span class="comment"># [3, 4, 5, 6, 7]</span></span><br><span class="line">l[:<span class="number">-1</span>]      <span class="comment"># [1, 2, 3, 4, 5, 6]</span></span><br><span class="line">l[<span class="number">-3</span>,<span class="number">-1</span>]    <span class="comment"># [5, 6]</span></span><br><span class="line">l[:<span class="number">20</span>]      <span class="comment"># [1, 2, 3, 4, 5, 6, 7]</span></span><br><span class="line">l[<span class="number">-20</span>:]     <span class="comment"># [1, 2, 3, 4, 5, 6, 7]</span></span><br></pre></td></tr></table></figure></li></ul><p>切片在start end 之外还提供stride 参数，表示步进，每隔stride 取一个元素，<code>somelist[start: end: stride]</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">l[::<span class="number">-1</span>]     <span class="comment"># 翻转列表</span></span><br><span class="line">l[::<span class="number">2</span>]      <span class="comment"># 取下标为偶数的元素</span></span><br><span class="line">l[<span class="number">1</span>::<span class="number">2</span>]     <span class="comment"># 取下标为奇数的元素</span></span><br></pre></td></tr></table></figure></p><p>当stride 为负数的时候表示从最后一个元素开始，并向前移动；如无必要尽量不要使用负数的stride，为了避免理解困难尽量不要同时指定三个参数，可以分成两步实现，一步切片，一步步进。</p><h1 id="列表推导式"><a href="#列表推导式" class="headerlink" title="列表推导式"></a>列表推导式</h1><p>List Comprehensions 是Python最强大的特性之一，它提供了紧凑的语法从一个<code>list</code> 生成另一个<code>list</code>，比用<code>map()</code>方式更清晰，也更灵活，并且支持filter，先来感受一下：<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><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"># 求列表中所有元素的平方</span></span><br><span class="line">a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">s = [i**<span class="number">2</span> <span class="keyword">for</span> i <span class="keyword">in</span> a]                   <span class="comment"># s = [1, 4, 9]</span></span><br><span class="line">等效于：</span><br><span class="line">s = map(<span class="keyword">lambda</span> x: x**<span class="number">2</span>, a)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 求列表中所有偶数元素的平方</span></span><br><span class="line">s2 = [i**<span class="number">2</span> <span class="keyword">for</span> i <span class="keyword">in</span> a <span class="keyword">if</span> i % <span class="number">2</span> == <span class="number">0</span>]    <span class="comment"># s2 = [4]</span></span><br></pre></td></tr></table></figure></p><p>除了list， dict 和set 也支持列表推导式。</p><h1 id="生成器"><a href="#生成器" class="headerlink" title="生成器"></a>生成器</h1><p>通过列表推导式我们处理输入序列的每一个元素得到一个新的列表，对于元素个数不多的序列这样没有问题，一旦元素多了就可能消耗过多内存导致程序崩溃，生成器解决了这个问题，它返回一个迭代器iterator， 而不是整个列表，不对括号内的表达式立即求值，需要的时候才进行运算，迭代地生成结果，像lazy calculate。感受一下，创建generator 的方法很简单，只需要把列表推导式的<code>[]</code> 换成<code>()</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><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">a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">g = (i**<span class="number">2</span> <span class="keyword">for</span> i <span class="keyword">in</span> a)   <span class="comment"># g是一个生成器</span></span><br><span class="line">print(g.next())         <span class="comment"># 不断调用next()计算下一个元素的值，直到结束</span></span><br><span class="line">print(g.next())         <span class="comment"># 不断调用next()计算下一个元素的值，直到结束</span></span><br><span class="line">print(g.next())         <span class="comment"># 不断调用next()计算下一个元素的值，直到结束</span></span><br><span class="line">print(g.next())         <span class="comment"># 不断调用next()计算下一个元素的值，直到结束</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 也可以用for遍历</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> g: </span><br><span class="line">    <span class="keyword">print</span> i</span><br></pre></td></tr></table></figure></p><p>另一种定义生成器的方式是使用<code>yield</code>关键字，使用<code>yield</code>可以让函数生成一个结果序列，而不仅仅是一个值；<br><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></pre></td><td class="code"><pre><span class="line">def countdown(n)</span><br><span class="line">    print &apos;Counting down&apos;</span><br><span class="line">    while n &gt; 0:</span><br><span class="line">        yield n     #生成一个值（n）</span><br><span class="line">        n -= 1</span><br></pre></td></tr></table></figure></p><p><strong>任何使用<code>yield</code>的函数都称为生成器</strong>，生成器是一个函数，它生成一个序列，以便在迭代中使用；调用生成器函数将创建一个对象，该对象通过连续调用<code>next()</code>方法生成结果序列；对象每次调用<code>next()</code>遇到<code>yield</code>返回，再次执行时从上次返回的<code>yield</code>处继续执行，遇到return或者最后一条语句结束；生成器传递给<code>yield</code>的任何值都会返回给调用者；生成器的微妙之处在与它常常可以与其他可迭代的对象（列表/文件等）混合在一起使用；<br>在需要返回一个列表的时候用生成器函数代替，首先代码会更清晰，其次使用生成器不必担心占用过多内存，因为它不必保存所有的输入（比如当输入是流的时候，对于文件也能逐行读取）和输出；</p><h1 id="enumerate-amp-range"><a href="#enumerate-amp-range" class="headerlink" title="enumerate &amp; range"></a><code>enumerate</code> &amp; <code>range</code></h1><p>内建方法<code>range()</code>遍历一组数字，在Python 2中，<code>range()</code>返回列表，<code>xrange()</code>迭代器，Python 3中取消了<code>xrange()</code>只保留<code>range()</code>，返回迭代器。<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="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">10</span>):</span><br><span class="line">    <span class="keyword">print</span> i**<span class="number">2</span></span><br></pre></td></tr></table></figure></p><p>有时我们在遍历一个列表的时候还希望知道当前元素的下标，这时可以使用索引迭代<code>enumerate()</code>，它和<code>range()</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><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">a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="keyword">for</span> i, value <span class="keyword">in</span> enumerate(a):</span><br><span class="line">    print(<span class="string">f'<span class="subst">&#123;i&#125;</span>:<span class="subst">&#123;value**<span class="number">2</span>&#125;</span>'</span>)</span><br><span class="line">    </span><br><span class="line"><span class="number">0</span>:<span class="number">1</span></span><br><span class="line"><span class="number">1</span>:<span class="number">4</span></span><br><span class="line"><span class="number">2</span>:<span class="number">9</span></span><br></pre></td></tr></table></figure></p><p>这里用到了<code>f前缀</code>，Python 3.6的新特性，方便格式化字符串；<br>可以给<code>enumerate()</code>提供第二个参数指定<code>i</code>从哪个数字数起，默认是0， 比如：<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><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">a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="keyword">for</span> i, value <span class="keyword">in</span> enumerate(a, <span class="number">1</span>):</span><br><span class="line">    print(<span class="string">f'<span class="subst">&#123;i&#125;</span>:<span class="subst">&#123;value**<span class="number">2</span>&#125;</span>'</span>)</span><br><span class="line">    </span><br><span class="line"><span class="number">1</span>:<span class="number">1</span></span><br><span class="line"><span class="number">2</span>:<span class="number">4</span></span><br><span class="line"><span class="number">3</span>:<span class="number">9</span></span><br></pre></td></tr></table></figure></p><h1 id="zip"><a href="#zip" class="headerlink" title="zip"></a>zip</h1><p><code>zip()</code>同时遍历多个可迭代对象，将他们打包成<code>tuple</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><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">names = [<span class="string">'Haku'</span>, <span class="string">'Vincent'</span>, <span class="string">'G'</span>]</span><br><span class="line">numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="keyword">for</span> name, number <span class="keyword">in</span> zip(names, numbers):</span><br><span class="line">    print(<span class="string">f'<span class="subst">&#123;name&#125;</span>: <span class="subst">&#123;number&#125;</span>'</span>)</span><br><span class="line">    </span><br><span class="line">Output:    </span><br><span class="line">Haku: <span class="number">1</span></span><br><span class="line">Vincent: <span class="number">2</span></span><br><span class="line">G: <span class="number">3</span></span><br></pre></td></tr></table></figure></p><p>Python 2中<code>zip()</code>返回包含<code>tuple</code> 的<code>list</code>，有可能占用过多内存，Python 3中返回的是产生<code>tuple</code> 的生成器。<code>zip</code>对象可以直接转换成<code>list</code> 或者<code>dict</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">z = zip(names, numbers)</span><br><span class="line">list(z)     <span class="comment"># 转换成list [('Haku', 1), ('Vincent', 2), ('G', 3)]</span></span><br><span class="line">dict(z)     <span class="comment"># 转换成dict &#123;'Haku': 1, 'Vincent': 2, 'G': 3&#125;</span></span><br></pre></td></tr></table></figure></p><p>如果两个可迭代对象长度不同，<code>zip()</code>会自动截断较长的一个，以较短的为准。</p><h1 id="紧跟在循环后面的else"><a href="#紧跟在循环后面的else" class="headerlink" title="紧跟在循环后面的else"></a>紧跟在循环后面的<code>else</code></h1><p>Python 的循环有一个其他语言没有的特性，它后面可以紧跟一个<code>else</code>代码块，除非在循环体中执行到<code>break</code>语句，否则<code>else</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><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">3</span>):</span><br><span class="line">    print(i)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    print(<span class="string">'Else block.'</span>)</span><br></pre></td></tr></table></figure></p><p>循环体中没有执行到<code>break</code>语句，所以会输出<code>Else block.</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><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="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">3</span>):</span><br><span class="line">    print(i)</span><br><span class="line">    <span class="keyword">if</span> i == <span class="number">2</span>:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    print(<span class="string">'Else block.'</span>)</span><br></pre></td></tr></table></figure></p><p>循环体中执行了<code>break</code>语句，所以不会输出<code>Else block.</code>;<br>除非知道自己在做什么，否则尽量不用 <code>for...else...</code> <code>while...else...</code> 这种用法。</p><h1 id="上下文管理器"><a href="#上下文管理器" class="headerlink" title="上下文管理器"></a>上下文管理器</h1><p>with语句支持在另一个被称为<em>上下文管理器</em>的对象的控制下执行一系列语句，适用于系统资源或执行环境相关的对象，用来简化异常处理；执行with语句时就会调用<code>__enter__()</code>方法，只要控制流离开与with相关的语句就会调用<code>__exit__()</code>方法；</p><h1 id="浅复制和深复制"><a href="#浅复制和深复制" class="headerlink" title="浅复制和深复制"></a>浅复制和深复制</h1><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">a = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]</span><br><span class="line">b = a</span><br></pre></td></tr></table></figure><p>这个例子中， a和b引用的是同一个对象，修改其中任意一个变量都会影响另一个，为了避免这种情况应该创建对象的副本，而不是新引用；对于列表和字典这样的容器对象，可以使用两种复制操作：深复制和浅复制；浅复制将创建一个新对象，但他包含的是对原始对象中包含的项的引用（）；深复制将创建一个新对象，并且递归地复制它所包含的对象，用标准库的<code>copy.deepcopy()</code>函数可以实现深复制；</p><h1 id="垃圾回收"><a href="#垃圾回收" class="headerlink" title="垃圾回收"></a>垃圾回收</h1><p>Python通过引用计数机制实现自动垃圾回收功能，Python中的每个对象都有一个引用计数，用来计数该对象在不同场所分别被引用了多少次。无论是给对象分配一个新的名称，还是将其放入一个容器中，该对象的引用计数就会增加1，每当消毁一次Python对象，则相应的引用就减1，只有当引用计数为零时，才真正从内存中删除Python对象。</p><h1 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h1><h2 id="值传递和引用传递"><a href="#值传递和引用传递" class="headerlink" title="值传递和引用传递"></a>值传递和引用传递</h2><p>python的参数属于引用传递还是值传递不由用户决定，而是根据参数是否可变决定，可变参数应用引用传递，不可变参数应用值传递，为了实现不可变参数的引用传递，可以先把他变成一个可变参数，如int型转换成包含一个int的list，这样list的值就可以改变，相当于改变了int；实参发生变化；或者对公用的数据使用全局变量；</p><h2 id="默认参数"><a href="#默认参数" class="headerlink" title="默认参数"></a>默认参数</h2><p>使用可变对象作为默认参数会导致意外情况发生，默认参数会保留前面调用产生的更改，为防止这种情况发生，最好将可变参数默认值设置为<code>None</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><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">add_to</span><span class="params">(somelist=[])</span>:</span></span><br><span class="line">    somelist.append(<span class="string">'A'</span>)</span><br><span class="line">    print(somelist)</span><br><span class="line"></span><br><span class="line">add_to()    <span class="comment"># ['A']</span></span><br><span class="line">add_to()    <span class="comment"># ['A', 'A']</span></span><br><span class="line">add_to()    <span class="comment"># ['A', 'A', 'A']</span></span><br></pre></td></tr></table></figure></p><p>上述代码中，三次执行方法得到的结果不同，与我们的预期不相符，直觉上，三次调用都没有传参，都是使用默认参数空列表<code>[]</code>，应该结果一样才是；而实际上如上一条所述，可变参数应用引用传递，函数定义的时候默认参数<code>somelist</code>的值就确定了，它指向<code>[]</code>，每次调用函数如果改变了它的值，那么下次调用的时候就会用改变后的值，<strong>默认参数只会计算一次</strong>；为了达到预期，可作如下修改，将默认参数的默认值设置为不可变对象<code>None</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><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">add_to</span><span class="params">(somelist=None)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> somelist <span class="keyword">is</span> <span class="keyword">None</span>:</span><br><span class="line">        somelist = []</span><br><span class="line">    somelist.append(<span class="string">'A'</span>)</span><br><span class="line">    print(somelist)</span><br></pre></td></tr></table></figure></p><h2 id="可变参数"><a href="#可变参数" class="headerlink" title="可变参数"></a>可变参数</h2><p>函数可以接受不定数量（0个或多个）的参数，称为可变参数，用法是在参数前加上星号<code>*</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><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="function"><span class="keyword">def</span> <span class="title">calc</span><span class="params">(*numbers)</span>:</span></span><br><span class="line">    sum = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> n <span class="keyword">in</span> numbers:</span><br><span class="line">        sum += n</span><br><span class="line">    <span class="keyword">return</span> sum</span><br><span class="line">        </span><br><span class="line">disp(<span class="number">1</span>, <span class="number">2</span>)          <span class="comment"># 返回3</span></span><br><span class="line">disp(<span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>)       <span class="comment"># 返回9</span></span><br></pre></td></tr></table></figure></p><p>可变参数在传递给函数之前会首先被转换成<code>tuple</code>, 函数感知到的是接受一个<code>tuple</code>类型参数，如果已经有了一个<code>tuple</code>或<code>list</code>，要使用可变参数，只需要在调用时给该参数加上<code>*</code>前缀：<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">num = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">calc(*num)          <span class="comment"># 返回6</span></span><br></pre></td></tr></table></figure></p><p>顺便提一句，静态语言需要用函数重载来解决可变参数类型和可变参数个数问题，python作为一种动态语言可以接受任意类型的参数，不存在第一种情况，而第二种情况可以可以通过设定默认参数和可变参数来解决；</p><h2 id="关键字参数"><a href="#关键字参数" class="headerlink" title="关键字参数"></a>关键字参数</h2><p>传参的时候可以带上关键字，位置参数也可以通过关键字传入，只要位置参数都已指定，关键字参数的顺序没有限制，位置参数没有全部指定时，不能使用关键字参数：<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><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="function"><span class="keyword">def</span> <span class="title">func</span><span class="params">(name, age)</span>:</span></span><br><span class="line">    print(<span class="string">f'<span class="subst">&#123;name&#125;</span>: <span class="subst">&#123;age&#125;</span>'</span>)</span><br><span class="line">    </span><br><span class="line">func(<span class="string">'Haku'</span>, <span class="number">18</span>)</span><br><span class="line">func(<span class="string">'Haku'</span>, age=<span class="number">18</span>)</span><br><span class="line">func(name=<span class="string">'Haku'</span>, age=<span class="number">18</span>)</span><br><span class="line">func(age=<span class="number">18</span>, name=<span class="string">'Haku'</span>)</span><br><span class="line"></span><br><span class="line">func(name=<span class="string">'Haku'</span>, <span class="number">17</span>)       <span class="comment"># Error  不能在关键字参数后面跟着位置参数</span></span><br><span class="line">func(<span class="number">17</span>, name=<span class="string">'Haku'</span>)       <span class="comment"># Error  17被认为是name参数，后面关键字再次传入了name</span></span><br></pre></td></tr></table></figure></p><p>关键字参数允许以dict 形式传入0个或多个参数名的参数，用<code>**</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><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">func</span><span class="params">(**kw)</span>:</span></span><br><span class="line">    print(kw)</span><br><span class="line"></span><br><span class="line">func(a=<span class="number">1</span>, b=<span class="number">2</span>)              <span class="comment"># 输出&#123;'a': 1, 'b': 2&#125;</span></span><br><span class="line"></span><br><span class="line">d = &#123;<span class="string">'a'</span>: <span class="number">1</span>, <span class="string">'b'</span>: <span class="number">2</span>&#125;</span><br><span class="line">func(**d)                   <span class="comment"># 输出&#123;'a': 1, 'b': 2&#125;</span></span><br></pre></td></tr></table></figure></p><h2 id="参数顺序"><a href="#参数顺序" class="headerlink" title="参数顺序"></a>参数顺序</h2><p>按照位置参数，默认参数，可变参数，关键字参数的顺序定义参数；</p><h2 id="强制关键字"><a href="#强制关键字" class="headerlink" title="强制关键字"></a>强制关键字</h2><p>强制传参是带上关键字，使函数目的更明确，避免混淆参数位置带来的意外：<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><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="function"><span class="keyword">def</span> <span class="title">to_upper</span><span class="params">(my_str, initial_only=False, limit_len=False)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> ignore_case:</span><br><span class="line">        ...</span><br><span class="line">    <span class="keyword">if</span> limit_len:</span><br><span class="line">        ...</span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">to_upper(<span class="string">'haku'</span>, <span class="keyword">False</span>, <span class="keyword">False</span>)</span><br></pre></td></tr></table></figure></p><p><code>func()</code>函数将字符串<code>my_str</code>转换成大写格式，<code>initial_only</code>参数表示是否只将首字母大写， <code>limit_len</code>参数表示是否限制给定字符串长度;  函数调用如下：<br><code>to_upper(&#39;haku&#39;, False, False)</code><br>如此函数可以正常调用，但是容易混淆两个<code>Boolean</code>参数，并且难以<code>Debug</code>, 更好的调用方式应该是调用者明确意图：<br><code>to_upper(&#39;haku&#39;, initial_only=False, limit_len=False)</code><br>重新定义<code>to_upper()</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><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="function"><span class="keyword">def</span> <span class="title">to_upper</span><span class="params">(my_str, *, initial_only=False, limit_len=False)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> ignore_case:</span><br><span class="line">        ...</span><br><span class="line">    <span class="keyword">if</span> limit_len:</span><br><span class="line">        ...</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure></p><h1 id="作用域"><a href="#作用域" class="headerlink" title="作用域"></a>作用域</h1><p>每执行一个函数时，就会创建局部命名空间，解析变量时解释器首先搜索局部命名空间，再搜索全局命名空间，最后检查内置命名空间，仍然找不到则引发<code>NameError</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">i = <span class="number">0</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">foo</span><span class="params">()</span>:</span></span><br><span class="line">    i = i + <span class="number">1</span>   <span class="comment">#i是局部变量，在赋值前尝试读取i的值会失败</span></span><br></pre></td></tr></table></figure></p><p>在函数中对变量进行赋值时，这些变量始终绑定到该函数的局部命名空间中，使用<code>global</code>语句可以改变这种行为；尽管可以访问嵌套作用域中的变量，Python 2只支持在最里层的作用域和全局命名空间（global）中给变量重新赋值，因此内部函数不能给定义在外部函数中的局部变量重新赋值，解决方法时把要修改的值放在列表或者字典里；在Python 3里可以将变量声明为<code>nonlocal</code>，<code>nonlocal</code>声明不会将名称绑定到任意函数中定义的局部变量，而是搜索当前调用栈中的下一层函数定义，即<em>动态作用域</em>；</p><h1 id="装饰器"><a href="#装饰器" class="headerlink" title="装饰器"></a>装饰器</h1><h2 id="property-装饰器"><a href="#property-装饰器" class="headerlink" title="@property 装饰器"></a>@property 装饰器</h2><p>python 内置的 @property装饰器将一个方法变成属性来调用，既能避免使用set get方法的繁琐，又保留了参数检查的功能；设置property后，对属性的访问将由一系列用户定义的get，set，delete函数控制；这种属性控制可以通过描述符对象进一步扩展；</p><h1 id="闭包Closure"><a href="#闭包Closure" class="headerlink" title="闭包Closure"></a>闭包Closure</h1><p>将组成函数的语句和函数的执行环境打包在一起，成为闭包。如果需要在一系列函数调用中保持某个状态，使用闭包是一种高效的方法；把函数当数据处理时，它将显式地携带与定义该函数的周围环境相关的信息；使用嵌套函数时，闭包将捕捉内部函数执行所需要的整个环境；如果需要编写惰性求值或者延迟求值的代码，闭包将会特别有用（通过嵌套函数的方式，返回函数）；<br>Python 支持闭包的前提是所有对象都是<em>第一类</em>的，意思是能够命名的对象都可以当做数据处理；函数是第一类对象，可以直接引用，也可以把它们赋值给变量，把它们作为参数传递个别的函数等等；</p><h1 id="协程-Coroutine"><a href="#协程-Coroutine" class="headerlink" title="协程 Coroutine"></a>协程 Coroutine</h1><p>参考：<br><a href="https://effectivepython.com/" target="_blank" rel="noopener">《Effective Python》</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;Python版本&quot;&gt;&lt;a href=&quot;#Python版本&quot; class=&quot;headerlink&quot; title=&quot;Python版本&quot;&gt;&lt;/a&gt;Python版本&lt;/h1&gt;&lt;p&gt;目前python有两个大版本，Python 2 和Python 3， 这两个版本互不兼容，除了语法上的差异，一些Python 2 的类库没有对应的Python 3 版本，在Python 3中无法使用，Python 3类库也可能不能用在Python 2 中。鉴于python 社区更关注Python 3 的特性和提升，针对Python 2 的更新范围包括bug修复与安全更新，，并且越来越多的开发者企业逐渐放弃对Python 2 的支持，对于新项目Python 3 是一个更长远的选择。不过有虚拟环境的支持，使用不同版本的python也不是问题。&lt;br&gt;安装python的时候默认的解释器是使用C语言开发的CPython，它编译python代码生成字节码然后执行，同样流行的还有JPython（Java），IronPython（.Net），PyPy（python）这些实现，其中CPython的使用更广泛。&lt;/p&gt;
    
    </summary>
    
    
      <category term="python" scheme="http://b.kohaku.cc/tags/python/"/>
    
      <category term="编码问题" scheme="http://b.kohaku.cc/tags/%E7%BC%96%E7%A0%81%E9%97%AE%E9%A2%98/"/>
    
      <category term="encode" scheme="http://b.kohaku.cc/tags/encode/"/>
    
      <category term="decode" scheme="http://b.kohaku.cc/tags/decode/"/>
    
  </entry>
  
  <entry>
    <title>区块链初探（二）</title>
    <link href="http://b.kohaku.cc/2018/04/11/blockchain-2/"/>
    <id>http://b.kohaku.cc/2018/04/11/blockchain-2/</id>
    <published>2018-04-11T02:13:32.000Z</published>
    <updated>2018-04-11T06:04:06.771Z</updated>
    
    <content type="html"><![CDATA[<h1 id="比特币的加密体系"><a href="#比特币的加密体系" class="headerlink" title="比特币的加密体系"></a>比特币的加密体系</h1><p>比特币采用非对称加密体系，使用椭圆曲线加密算法，私钥长度32字节，公钥压缩后32字节；<br>私钥通过椭圆曲线数字签名算法生成公钥，公钥通过哈希生成地址，这两个过程都是单向不可逆的。<br>在一次交易中，私钥用来签名，公钥用来验签。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;比特币的加密体系&quot;&gt;&lt;a href=&quot;#比特币的加密体系&quot; class=&quot;headerlink&quot; title=&quot;比特币的加密体系&quot;&gt;&lt;/a&gt;比特币的加密体系&lt;/h1&gt;&lt;p&gt;比特币采用非对称加密体系，使用椭圆曲线加密算法，私钥长度32字节，公钥压缩后32字节；&lt;br
      
    
    </summary>
    
    
      <category term="Blockchain" scheme="http://b.kohaku.cc/tags/Blockchain/"/>
    
      <category term="Bitcoin" scheme="http://b.kohaku.cc/tags/Bitcoin/"/>
    
  </entry>
  
  <entry>
    <title>区块链初探（一）</title>
    <link href="http://b.kohaku.cc/2018/03/06/blockchain-1/"/>
    <id>http://b.kohaku.cc/2018/03/06/blockchain-1/</id>
    <published>2018-03-06T03:24:34.000Z</published>
    <updated>2018-04-23T06:30:12.898Z</updated>
    
    <content type="html"><![CDATA[<p>区块链（Blockchain）最初于2008年被应用于比特币（加密电子货币）上，作为比特币交易的账本；区块链因比特币进入大众视野，发明者中本聪由此名声大振，然而其真实身份至今仍是一个谜，这在互联网时代是一件令人匪夷所思的事情；基于区块链技术的比特币成为了第一个解决了<a href="#两军问题">两军问题</a>的电子货币，与传统货币不同的是，它不需要一个受信任的权威机构或是中心服务器。比特币被发明之后以极快的速度发展，从随处可闻的挖矿新闻就能略知一二，虚拟货币在全球掀起了一场风暴，其底层区块链技术成为各大机构和公司研究的对象。</p><a id="more"></a><h1 id="区块链是什么"><a href="#区块链是什么" class="headerlink" title="区块链是什么"></a>区块链是什么</h1><p>关于区块链，并没有一个统一明确的定义，有将其解释为一种分布式数据库，也有将其理解为一种去中心化的分布式账本，区块链虽然存储数据，但将其定义为数据库未免牵强，存储数据并不是它唯一关注的重点，而账本也仅是当我们提及比特币等电子货币的时候适用，比特币是区块链技术的一个应用，二者却并不等同。顾名思义，blockchain 是由<em>块</em>组成的一条<em>链</em>，Wikipedia 中的定义为：</p><blockquote><p>A <a href="https://en.wikipedia.org/wiki/Blockchain" target="_blank" rel="noopener">blockchain</a>, originally block chain, is a continuously growing list of records, called blocks, which are linked and secured using cryptography.</p></blockquote><p>区块链基于点对点的网络，由一系列加密数据块链接产生的，每一个区块都包含上一个区块的哈希值，时间戳，和交易数据，它被设计成不可修改，无法伪造（伪造代价巨大，几乎无法实现）。这让区块链生而适合一些特定场景，比如交易记录，医疗记录，物流跟踪，投票，文档、版权管理，产权转移等等；   </p><p>区块链2.0的概念提出于2014年，指基于分布式区块链数据库的新型应用，区块链2.0已经不止于交易，而是使价值交换不需要一个强势的中间人作为信息和金钱的仲裁者；</p><h2 id="去中心化"><a href="#去中心化" class="headerlink" title="去中心化"></a>去中心化</h2><p>提到区块链，听到最多的词是去中心化；在各个网络节点中存储数据，区块链技术避免了中心化存储数据，它没有中心节点；中心化的数据提供更好的可控性，信息和数据可以被修改，而非中心化系统的每一个节点都有区块链的完整拷贝，数据质量由所有节点共同保证，没有一个权威节点存在，所有节点的地位相同，每一条交易信息都会被广播到整个网络;</p><h2 id="区块链的结构"><a href="#区块链的结构" class="headerlink" title="区块链的结构"></a>区块链的结构</h2><p>区块链的数据库由点对点的网络和一个分布式时间戳服务器自动管理，就比特币应用来讲，区块链是一个去中心化的分布式账本，在多台计算机之间记录交易，在不修改后续所有子块的前提下，记录的生成是不可逆的（如果有人修改了一个区块，该区块的哈希值就改变了，由于后一个区块记录这前一个区块的哈希值，要使区块修改之后仍在链中，必须依次修改所有的后续区块，哈希计算被设计成非常困难，因此区块修改难以实现，除非攻击者掌握了51%以上的计算能力），区块链的可靠性因此保证，数据一旦上链便无法篡改；区块链也被描述成一种价值交换协议，相较于传统方法，基于区块链的价值交换可以更快捷，成本更低，理想情况下也更安全；</p><h2 id="区块"><a href="#区块" class="headerlink" title="区块"></a>区块</h2><p>一个区块由区块头和区块体组成，区块头记录当前区块的特征信息（上一个区块的加密哈希，时间戳，区块体的哈希 等），区块体记录实际的交易数据；</p><h2 id="区块时间"><a href="#区块时间" class="headerlink" title="区块时间"></a>区块时间</h2><p>区块时间是区块链网络生成一个新区块所花费的平均时间，比特币中生成一个区块的时间被设定成大约十分钟，通过设置计算难度系数动态调节，保证区块时间维持在这一平均水平，难度系数的不断调高，导致挖矿越来越难；</p><h2 id="区块分叉"><a href="#区块分叉" class="headerlink" title="区块分叉"></a>区块分叉</h2><p>有时候多个区块可能同时产生，他们拥有同一个前置区块，导致区块链分叉；对此，每个区块链都有特定的算法来给分叉打分，拥有更高评分的分支会被保留下来，其他的被抛弃，被抛弃的区块被称为孤儿区块；比特币中，如果区块有分叉，将保留先拥有6个新的后续区块的分支，也就是增长较快的分支，交易是否生效需等待一个小时（生成六个区块的时间）之后确认；</p><h2 id="挖矿"><a href="#挖矿" class="headerlink" title="挖矿"></a>挖矿</h2><p>所有人都能向区块链中提交区块，而且必须保证节点同步，所以添加新区块必须有限制，不能增加得太快，设计让添加区块变得困难，只有通过海量的计算才能得到当前区块的有效哈希，要得到正确的哈希只能靠猜，也就是进行hash碰撞，作为工作量的证明，猜对了就能得到比特币酬劳，这个计算的过程被称为挖矿；</p><h2 id="共识算法"><a href="#共识算法" class="headerlink" title="共识算法"></a>共识算法</h2><p>共识是指多方参与的节点基于既定的规则，对网络中的行为和数据达成一致；共识机制用来保障系统的一致性，让所有节点对新增区块达成共识；比特币在在生成区块的时候采用了PoW（Proof-of-work）共识算法，积累了最多工作量证明的链被当作有效的链（出现分叉的时候，生长最快的链进行了最多的计算）；其他的还有POS 股权证明，Practical Byzantine Fault Tolerance 等共识算法；<br><img src="/2018/03/06/blockchain-1/bloackchain-trust.gif"></p><h2 id="比特币工作原理"><a href="#比特币工作原理" class="headerlink" title="比特币工作原理"></a>比特币工作原理</h2><img src="/2018/03/06/blockchain-1/bitcoin.jpg"><h1 id="区块链的短板"><a href="#区块链的短板" class="headerlink" title="区块链的短板"></a>区块链的短板</h1><p>虽然说需要全网51%以上的计算能力才能篡改区块链中的信息，但是对于私有链，其本身已经掌握了100%的创造区块的资源，只要攻破了私有服务器上的区块创造工具，就可以随意修改交易信息，因此一些内部的区块链方案并没有解决什么问题，只是增加了笨重的数据库而已；区块链的安全性目前也没有想象中那么高；2018年3月7日币安黑客事件引起虚拟货币恐慌，黑客利用大量做空获利，交易被记录被回退，解释是记录在交易所手里还没有上区块链；</p><h1 id="区块链的应用场景"><a href="#区块链的应用场景" class="headerlink" title="区块链的应用场景"></a>区块链的应用场景</h1><p>当下区块链技术最主要的应用场景是作为加密电子货币的分布式账本，尤其是比特币，美国，中国等国家的银行也在研究区块链在中心化银行业的应用；</p><h2 id="智能合约"><a href="#智能合约" class="headerlink" title="智能合约"></a>智能合约</h2><p>智能合约是区块链研究者常常会提到的一个词，基于区块链的智能合约是指合约在没有人为干预的情况下部分或者全部地自动执行，它的一个主要目标是把合约交由区块链保管等待条件具备然后自动执行；<br>智能合约存在的一个明显问题是：条件满足是否仍需要一个中心化的第三方机构提供数据；</p><h2 id="银行业"><a href="#银行业" class="headerlink" title="银行业"></a>银行业</h2><p>区块链技术用于银行的清算与结算系统大大提高效率；一些中心化银行正在探索转移部分支付业务到区块链上，甚至开发电子货币；区块链在票据市场的应用，如央行推出的区块链数字票据交易平台；</p><h2 id="溯源"><a href="#溯源" class="headerlink" title="溯源"></a>溯源</h2><p>使用区块链技术进行商品溯源和防伪，查看物流信息；也有将区块链应用在公益事业上做善款追踪；</p><h1 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h1><h2 id="两军问题"><a href="#两军问题" class="headerlink" title="两军问题"></a>两军问题</h2><p><a href="https://en.wikipedia.org/wiki/Two_Generals%27_Problem" target="_blank" rel="noopener">两军问题</a>，假设有两只军队分别有两个将军指挥，他们分别在堡垒的两个方向，只有同时发起进攻才能胜利攻下堡垒，如果贸然单独进攻则有被击败的风险，于是两军需要约定共同发起攻击的时间，但是他们之间通信必然要经过堡垒，有可能被堡垒中的敌军截获情报；背景介绍至此，假设第一个将军发送信息，约定‘次日早晨8点一起进攻’，信息发出之后，他无法确认对方将军是否收到了他的信息，因此他不会贸然出击，为了确认，第二个将军会发出信息‘我收到了你的消息，次日早晨8点一起进攻’，他发出消息之后，无法确认第一个将军是否会收到他的回信，因此也会犹豫是否出击，这样，无论进行多少轮确认，总有一方无法确认他们的最后一条消息是否成功被送达，根本原因是他们在通过一个不可靠的信道通信。<br>两军问题很容易让我们联想到TCP 的三次握手协议，IP协议并不是一个可靠的通信协议，TCP在这个不可靠的信道上通过三次握手保证了基本的可用性，增加多少次握手次数也无法保证信道的可靠性；</p><h2 id="拜占庭将军问题"><a href="#拜占庭将军问题" class="headerlink" title="拜占庭将军问题"></a>拜占庭将军问题</h2><p><a href="https://en.wikipedia.org/wiki/Byzantine_fault_tolerance" target="_blank" rel="noopener">拜占庭将军问题</a>，一组拜占庭将军各自率领一致军队包围一座城市，这些将军希望规划一个攻城计划，将军们必须决定是进攻还是撤退，其中一些将军支持进攻，另外一些支持撤退，他们同意共同决定最后的计划，因为部分军队进攻部分军队撤退会造成严重后果；这个问题因为将军中可能存在叛徒变得复杂起来，这些叛徒将军可能会投票给糟糕的策略，甚至执行糟糕的决策，比如有九位将军，其中四位支持进攻，四位支持撤退，第九位是叛徒将军，故意向支持进攻的四位将军送信表示投票进攻，向另外四位支持撤退的将军送信表示投票撤退，这样就会造成部分军队进攻，部分撤退，对军队整体造成破坏；不仅如此，将军之间的信件是否能被信使顺利送达，新建是否会被伪造篡改都会使问题变得更复杂；只有当多数忠诚将军在战略上达成统一，可称达到了拜占庭容错；</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;区块链（Blockchain）最初于2008年被应用于比特币（加密电子货币）上，作为比特币交易的账本；区块链因比特币进入大众视野，发明者中本聪由此名声大振，然而其真实身份至今仍是一个谜，这在互联网时代是一件令人匪夷所思的事情；基于区块链技术的比特币成为了第一个解决了&lt;a href=&quot;#两军问题&quot;&gt;两军问题&lt;/a&gt;的电子货币，与传统货币不同的是，它不需要一个受信任的权威机构或是中心服务器。比特币被发明之后以极快的速度发展，从随处可闻的挖矿新闻就能略知一二，虚拟货币在全球掀起了一场风暴，其底层区块链技术成为各大机构和公司研究的对象。&lt;/p&gt;
    
    </summary>
    
    
      <category term="blockchain" scheme="http://b.kohaku.cc/tags/blockchain/"/>
    
  </entry>
  
  <entry>
    <title>命令行工具库 fabric</title>
    <link href="http://b.kohaku.cc/2018/01/18/fabric/"/>
    <id>http://b.kohaku.cc/2018/01/18/fabric/</id>
    <published>2018-01-18T09:10:34.000Z</published>
    <updated>2018-01-19T07:54:48.558Z</updated>
    
    <content type="html"><![CDATA[<p>fabric 是一个python库（支持python2.5 到2.7 版本），也是一个命令行工具，，它的一个重要的功能是<strong>在不登录远程服务器的情况下，在本地执行远程shell命令，上传下载文件等等</strong>，这个特性让我们可以通过fabric 来进行应用部署，以及执行一些系统运维自动化的任务，而且是可以在多台服务器上执行。</p><blockquote><p>It provides a basic suite of operations for executing local or remote shell commands (normally or via sudo) and uploading/downloading files, as well as auxiliary functionality such as prompting the running user for input, or aborting execution.</p></blockquote><a id="more"></a><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><p>安装fabric：激活python2 虚拟环境，pip install fabric</p><h1 id="使用SSH-key"><a href="#使用SSH-key" class="headerlink" title="使用SSH key"></a>使用SSH key</h1><p>fabric 提供密码和密钥方式登录远程服务器，为了方便和安全起见，密钥是更好的选择。</p><h2 id="生成SSH-key"><a href="#生成SSH-key" class="headerlink" title="生成SSH key"></a>生成SSH key</h2><p>首先我们需要在远程服务器生成密钥，在远程执行：<br><code>$ ssh-keygen</code><br>在.ssh/ 目录下生成默认rsa算法加密的密钥对（id_rsa 私钥，id_rsa.pub 公钥）</p><h2 id="发布公钥"><a href="#发布公钥" class="headerlink" title="发布公钥"></a>发布公钥</h2><p>密钥生成之后，需要把公钥的内容添加到.ssh 目录下的 _authorized<em>keys</em> 文件，<br><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="built_in">cd</span> ~/.ssh</span><br><span class="line">$ cat id_rsa.pub &gt;&gt; authorized_keys</span><br><span class="line">```  </span><br><span class="line">修改权限</span><br><span class="line">```bash</span><br><span class="line">$ chmod 600 authorized_keys</span><br><span class="line">$ chmod 700 ~/.ssh</span><br></pre></td></tr></table></figure></p><h2 id="配置fabric-环境参数"><a href="#配置fabric-环境参数" class="headerlink" title="配置fabric 环境参数"></a>配置fabric 环境参数</h2><p>将私钥下载到本地，指定fabric 环境配置<br><code>env.key_filename = &quot;path/to/id_rsa&quot;</code></p><h1 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h1><p>fabric 提供的API 中，常用的有<code>local()</code> 和<code>run()</code>，分别在本地和远程服务器执行命令。fabric 文件的一个函数对应一个操作。 </p><p>举一个简单的例子，如需要列出远程服务器”/“下的文件和目录，在本地新建fabfile.py 文件如下：</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></pre></td><td class="code"><pre><span class="line">#!/usr/bin/python env</span><br><span class="line"># -*- coding: utf-8 -*-</span><br><span class="line"></span><br><span class="line">from fabric.api import *</span><br><span class="line">env.hosts=[&apos;username@xxx.xxx.xxx.xxx&apos;]</span><br><span class="line">env.key_filename = &quot;id_rsa&quot;   # 通过密钥登录远程服务器</span><br><span class="line"></span><br><span class="line">def hello():</span><br><span class="line">    with cd(&apos;/&apos;):     # 进入目录</span><br><span class="line">        run(&apos;ls -l&apos;)  # run() 执行远程操作 列出远程机器上的文件和目录</span><br></pre></td></tr></table></figure><p>执行 <code>fab hello</code> 命令列出文件和目录，和在远程服务器上执行一样。</p><p>fabfiles 通常放在项目的根目录。</p><p><a href="http://docs.fabfile.org/en/1.14/" target="_blank" rel="noopener">fabric 文档</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;fabric 是一个python库（支持python2.5 到2.7 版本），也是一个命令行工具，，它的一个重要的功能是&lt;strong&gt;在不登录远程服务器的情况下，在本地执行远程shell命令，上传下载文件等等&lt;/strong&gt;，这个特性让我们可以通过fabric 来进行应用部署，以及执行一些系统运维自动化的任务，而且是可以在多台服务器上执行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It provides a basic suite of operations for executing local or remote shell commands (normally or via sudo) and uploading/downloading files, as well as auxiliary functionality such as prompting the running user for input, or aborting execution.&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
      <category term="python" scheme="http://b.kohaku.cc/tags/python/"/>
    
      <category term="自动化部署" scheme="http://b.kohaku.cc/tags/%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2/"/>
    
  </entry>
  
  <entry>
    <title>Go into PyCharm</title>
    <link href="http://b.kohaku.cc/2018/01/17/go-into-pycharm/"/>
    <id>http://b.kohaku.cc/2018/01/17/go-into-pycharm/</id>
    <published>2018-01-17T07:53:01.000Z</published>
    <updated>2018-01-22T07:09:37.974Z</updated>
    
    <content type="html"><![CDATA[<p>开发Python项目首选PyCharm，PyCharm有很多好用的强大的特性，略作记录以备后用。</p><h1 id="Automatic-Upload"><a href="#Automatic-Upload" class="headerlink" title="Automatic Upload"></a>Automatic Upload</h1><p>除了集成Git 等版本控制工具，pycharm 还提供自动上传文件到远程主机，以及调用远程主机解释器的功能。配置好SSH连接到远程主机之后，在本地工作区所做的修改可以被自动同步到远程主机上.</p><a id="more"></a><ul><li>因为安全原因，我在远程主机禁止用户名密码方式，只允许私钥登录，因此需要提前准备Private key 文件以在IDE 中连接远程主机；</li></ul><blockquote><p><strong>PS:</strong> 在远程主机生成的ppk私钥文件不被pycharm 支持，需要通过pyttygen 工具进行转换：打开puttygen.exe， Conversation -&gt; Import 导入已生成的ppk 私钥文件，密码可选，然后 Conversation -&gt; Export OpenSSH Key 导出备用。</p></blockquote><ul><li>PyCharm菜单栏进入Tools -&gt; Deployment -&gt; Configuration，新增配置，类型选择SFTP，配置远程主机及端口，选择验证方式为Key Pair，私钥文件选择上一步骤导出的文件，测试SFTP 连接是否成功。</li></ul><img src="/2018/01/17/go-into-pycharm/deployment_config.png"><ul><li>在Mapping 标签页填上本地路径和远程主机发布路径。</li><li>远程主机配置完成之后可以继续在Deployment 下的Options 选项中做一些个性化的配置，比如排除哪些文件，是否自动上传等。</li></ul><h1 id="TODO"><a href="#TODO" class="headerlink" title="TODO"></a>TODO</h1><p>PyCharm 和其他jetBrain 的IDE 都有TODO 功能，迅速定位注释中出现TODO 关键字的位置，帮助我们快速回到上次工作遗留的地方。  </p><img src="/2018/01/17/go-into-pycharm/todo.png"><p>TODO 窗口包含四个标签页，显示不同范围的TODO。 此外，TODO 功能还支持自定义模板。</p><p><a href="https://www.jetbrains.com/help/pycharm/using-todo.html" target="_blank" rel="noopener">More about TODO</a></p><p><a href="https://www.jetbrains.com/help/pycharm/meet-pycharm.html" target="_blank" rel="noopener">PyCharm 帮助文档</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;开发Python项目首选PyCharm，PyCharm有很多好用的强大的特性，略作记录以备后用。&lt;/p&gt;
&lt;h1 id=&quot;Automatic-Upload&quot;&gt;&lt;a href=&quot;#Automatic-Upload&quot; class=&quot;headerlink&quot; title=&quot;Automatic Upload&quot;&gt;&lt;/a&gt;Automatic Upload&lt;/h1&gt;&lt;p&gt;除了集成Git 等版本控制工具，pycharm 还提供自动上传文件到远程主机，以及调用远程主机解释器的功能。配置好SSH连接到远程主机之后，在本地工作区所做的修改可以被自动同步到远程主机上.&lt;/p&gt;
    
    </summary>
    
    
      <category term="pycharm" scheme="http://b.kohaku.cc/tags/pycharm/"/>
    
      <category term="programming" scheme="http://b.kohaku.cc/tags/programming/"/>
    
  </entry>
  
  <entry>
    <title>Django 笔记</title>
    <link href="http://b.kohaku.cc/2018/01/12/django-notes/"/>
    <id>http://b.kohaku.cc/2018/01/12/django-notes/</id>
    <published>2018-01-12T06:59:48.000Z</published>
    <updated>2018-03-02T06:33:06.896Z</updated>
    
    <content type="html"><![CDATA[<p>Django是一个用python写的轻量级Web框架，虽说是轻量级，但是它能做的事情并不少，有了Django我们就可以用很少的代码来搭建一个现代化的网站，避免重复造轮子。Django2.0之后已经放弃了对python2 的支持，只支持python3.4 及以上版本。</p><p>Django框架包含了一个完整Web应用各方面的知识点，下面对Django一些常用的特性做一个梳理，旨在学习Django的架构和源码。</p><a id="more"></a><h1 id="URL-Route"><a href="#URL-Route" class="headerlink" title="URL Route"></a>URL Route</h1><h2 id="路由规则"><a href="#路由规则" class="headerlink" title="路由规则"></a>路由规则</h2><p>Django项目的路由规则写在一系列urls.py文件里，包括项目的urls.py和各个app的urls.py。当一个请求到来，在展示最终页面之前需要经过以下步骤：</p><ul><li>首先确定根URLconf，通常这个值在settings.py 中设置。但是如果传入的HttpRequest对象有urlconf属性，那么它将会作为当前请求的根URLconf，代替settings中的ROOT_URLCONF；</li><li>Django加载该URLconf 模块，找到urlpatterns变量；</li><li>Django<strong>依次</strong>匹配每一个url pattern，在第一个匹配所请求URL的pattern处停下来，因此pattern 的顺序是有影响的；</li><li>一旦一个URL pattern匹配成功，Django 会调用对应的视图，视图接收以下参数：<ul><li>一个HTTPRequest 实例;</li><li>如果匹配的URL pattern没有返回命名组，正则表达式匹配的内容将会作为positional parameters 提供给视图;</li><li>关键字参数由正则表达式匹配的命名组组成，可以被可选参数覆盖；</li></ul></li><li>如果没有匹配到任何URL pattern，或者在匹配过程中抛出了异常，Django会调用一个对应的错误处理视图，比如400，500等；</li></ul><blockquote><p>Django2.0 已经不在路由规则中使用符号 <code>^</code>、 <code>$</code> ，并且将<code>url</code>函数改成<code>path</code>函数。</p></blockquote><h3 id="传递额外的参数给视图"><a href="#传递额外的参数给视图" class="headerlink" title="传递额外的参数给视图"></a>传递额外的参数给视图</h3><p>URLconf提供了一个hook，允许以python dictionary的形式传递额外的参数给视图函数。<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">urlpatterns = [</span><br><span class="line">    path(<span class="string">'blog/&lt;int:year&gt;/'</span>, views.year_archive, &#123;<span class="string">'foo'</span>: <span class="string">'bar'</span>&#125;),</span><br><span class="line">]</span><br></pre></td></tr></table></figure></p><p>上例中，如果请求<code>/blog/2018/</code>，Django会调用 <code>views.year_archive(request, year=2018, foo=&#39;bar&#39;)</code></p><h2 id="静态文件"><a href="#静态文件" class="headerlink" title="静态文件"></a>静态文件</h2><p>在生产环境我们通常会使用单独的server来提供对静态文件的访问，Django提供了一个static()方法在调试的时候serve静态文件。<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">urlpatterns = [</span><br><span class="line">    <span class="comment"># ... the rest of your URLconf goes here ...</span></span><br><span class="line">] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)</span><br></pre></td></tr></table></figure></p><h1 id="Model"><a href="#Model" class="headerlink" title="Model"></a>Model</h1><p>Django已经包含了ORM，因此只需要关注Model类，而不用关心SQL。前提是要在INSTALLED_APPS 中包含指定的app。</p><blockquote><p><em>Tips：</em>如果使用PyCharm 的话，通过Ctrl + Alt + R 调出python manage.py 命令界面，可以直接执行migrate，shell 等命令，不用在python 命令行执行.</p></blockquote><h1 id="Middleware"><a href="#Middleware" class="headerlink" title="Middleware"></a>Middleware</h1><p>Django 里的Middleware 可以看成是一个hook，处于request/response 过程之间，是一个轻量级的、底层的插件系统，用于改变Django 的输入输出。</p><p>一个middleware factory 是一个callable，它接收一个get_response<br> callable作为参数，返回一个middleware；一个middleware 是一个callable，它像view一样，接收一个request， 返回一个response。返回真正的view 之前可能经历数个中间件：在响应阶段，调用视图之前，Django按顺序从上到下应用MIDDLEWARE中定义的中间件。可以把这个结构看成一个洋葱，洋葱的中心是最后展示的view，它被一层层的中间件包裹起来，每一层都调用get_response把request 传递到下一层，直到view，然后response也会按原路返回直到最外层的中间件。 在request 和response 之间想要做的事情，都可以用middleware 来实现。</p><h1 id="Authentication"><a href="#Authentication" class="headerlink" title="Authentication"></a>Authentication</h1><p>Django 的Authentication 系统处理用户组和账户，用户许可和基于cookie的session，实际上它包含Authentication 和Authorization 两部分，前者负责确认用户的身份，后者决定一个认证用户可以做的事情。</p><h2 id="自定义User模型"><a href="#自定义User模型" class="headerlink" title="自定义User模型"></a>自定义User模型</h2><p>User 对象是认证系统的核心，Django提供了默认的User模型，但是还是推荐使用自定义的user 模型，在将来提供更好的扩展性。</p><blockquote><p>If you’re starting a new project, it’s highly recommended to set up a custom user model, even if the default User model is sufficient for you. This model behaves identically to the default user model, but you’ll be able to customize it in the future if the need arises.</p></blockquote><p>使用自定义的User模型之前需要三个步骤：  </p><ul><li><p>继承AbstractUser 类：</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="class"><span class="keyword">class</span> <span class="title">User</span><span class="params">(AbstractUser)</span>:</span></span><br><span class="line">    <span class="keyword">pass</span></span><br></pre></td></tr></table></figure></li><li><p>settings.py 中指定 AUTH_USER_MODEL = ‘appname.User’</p></li><li>在app 的admin.py中注册User模型：<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> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"><span class="keyword">from</span> django.contrib.auth.admin <span class="keyword">import</span> UserAdmin</span><br><span class="line"><span class="keyword">from</span> .models <span class="keyword">import</span> User</span><br><span class="line"></span><br><span class="line">admin.site.register(User, UserAdmin)</span><br></pre></td></tr></table></figure></li></ul><h2 id="Authentication-Views"><a href="#Authentication-Views" class="headerlink" title="Authentication Views"></a>Authentication Views</h2><p>Django 提供了一些默认视图（Authentication Views）处理登录，注销和修改密码操作，可以直接拿来用。如果需要更多个性化的处理，不想使用内置的视图，在项目中新建一个app来处理这些用户操作。</p><h2 id="buit-in-forms"><a href="#buit-in-forms" class="headerlink" title="buit-in forms"></a>buit-in forms</h2><p>如果不想使用内置的视图，但又不想自己写各种表单，authentication 系统提供了一些内置的form。</p><h1 id="Testing"><a href="#Testing" class="headerlink" title="Testing"></a>Testing</h1><p>TDD 是敏捷开发的一个核心实践，通过测试来推动开发的进行，不写测试用例节省的时间有可能在将来付出成倍的代价。自动化测试可以发现迭代过程中对已有功能的不可预期的影响，及时解决问题。</p><p>Django 的单元测试使用Python的标准库unittest，通过class-based 的方法定义测试用例，执行测试的时候会检查所有以test 开头的文件。</p><h2 id="测试提速"><a href="#测试提速" class="headerlink" title="测试提速"></a>测试提速</h2><h3 id="测试并行"><a href="#测试并行" class="headerlink" title="测试并行"></a>测试并行</h3><p>如果测试用例相互独立，可以并行执行测试以提升效率。执行test 加上–parallel参数，比如<code>test --parallel=4</code></p><h3 id="保留测试数据库"><a href="#保留测试数据库" class="headerlink" title="保留测试数据库"></a>保留测试数据库</h3><p>保留上一次运行测试创建的册数数据库，节省创建和销毁操作，大大减少运行测试的时间。<code>test --keepdb</code></p><h3 id="密码哈希"><a href="#密码哈希" class="headerlink" title="密码哈希"></a>密码哈希</h3><p>默认的密码哈希算法相当耗时，如果需要在测试用例中大量认证用户，可以自定义hash 算法，在settings.py中设置PASSWORD_HASHERS，指定特定的哈希算法，比如<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">PASSWORD_HASHERS = [</span><br><span class="line">    <span class="string">'django.contrib.auth.hashers.MD5PasswordHasher'</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure></p><h2 id="测试工具"><a href="#测试工具" class="headerlink" title="测试工具"></a>测试工具</h2><h3 id="测试用户登陆状态"><a href="#测试用户登陆状态" class="headerlink" title="测试用户登陆状态"></a>测试用户登陆状态</h3><p>调用force_login方法模拟用户已登陆状态，无须先创建用户再模拟登陆：<br><code>self.client.force_login(User.objects.get_or_create(username=&#39;testuser&#39;)[0])</code></p><h3 id="测试静态文件"><a href="#测试静态文件" class="headerlink" title="测试静态文件"></a>测试静态文件</h3><p>调试和发布的时候常常会找不到静态文件，检查静态文件是否被正常serve<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"><span class="keyword">from</span> django.contrib.staticfiles <span class="keyword">import</span> finders</span><br><span class="line"></span><br><span class="line">result = finders.find(<span class="string">'css/base.css'</span>)</span><br></pre></td></tr></table></figure></p><p>如果找到静态文件，find() 会返回文件的全路径，否则返回None；</p><h3 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h3><p>Django 使用Python 内置的logging 模块来实现系统日志；一个日志配置包含四个部分：Loggers, Handlers, Filters, Formatters；</p><h2 id="基本定义"><a href="#基本定义" class="headerlink" title="基本定义"></a>基本定义</h2><p>Logger 是日志系统的入口，每一个logger 都是一个named bucket，信息被写入logger 以作后续操作；<strong>logger 有其日志级别</strong>，每一条日志记录也有其级别，只有日志记录的级别高于等于logger 级别的时候，该日志记录才会继续后续的处理，否则会被忽略；当logger 决定一条信息需要被处理的时候，该信息被传递给一个Handler；</p><p>Handler 是一个决定logger 中的每一条信息将如何被处理的引擎，它描述一个具体的日志行为，比如把信息输出到屏幕，文件或者socket； <strong>handler 也有级别</strong>，如果日志记录的级别低于handler 的级别，它会被忽略；一个logger 可以有多个handler， 每个handler 可以有不同的日志级别，这样可以根据信息的重要性来提供不同形式的通知，比如可以单独使用一个handler 处理CRITICAL 级别的日志记录，发送告警，同时用另一个handler 处理所有日志记录，写入文件以备后续分析；</p><p>Filter 对日志从logger 传递到handler 提供更多的控制，默认情况下，任何符合级别要求的日志记录都会被处理，加上过滤器之后可以给日志处理增加额外的条件；Filter 还可以在日志记录被发出之前对它进行修改，比如写一个过滤器，在符合特定条件的情况下，把日志记录的级别从ERROR 降到WARNING；filter 可以被用在logger 或者handler 上，多个filters 可以通过链式实现过滤操作；一个比较常见的filter 是require_debug_false， 当DEBUG设置为False才处理日志记录；</p><p>Formatter 描述具体的文本格式，将日志记录渲染成文本；</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">logger ---------&gt; handlers ---------&gt; formatter ---&gt; files, emails .etc</span><br><span class="line">        filters             filters</span><br></pre></td></tr></table></figure></p><h2 id="Django-的日志组件"><a href="#Django-的日志组件" class="headerlink" title="Django 的日志组件"></a>Django 的日志组件</h2><p>Django 提供了一些工具来处理Web 服务环境下的日志，包括内置的一些loggers, filters 和一个AdminEmailHandler；如果logging 配置字典中disable_existing_loggers 值设置成True, 默认配置中的所有logger 会被禁用（禁用不同于移除，这些logger 依然存在，但会丢弃所有给它的内容），为了避免预期之外的情况，最好将其设置成False，如果需要可重定义一些默认的logger；</p><h3 id="AdminEmailHandler"><a href="#AdminEmailHandler" class="headerlink" title="AdminEmailHandler"></a>AdminEmailHandler</h3><p>顺带提一句日志配置中的邮件设置，当DEGUG设置为False 的时候，如果在setting.py 中同时设置了ADMINS参数，Django 会在发生500错误的时候发送邮件给ADMINS 中的用户；默认情况下，Django 将从root@localhost 发送邮件，为了保证邮件正确接收，需要设置SERVER_EMAIL 参数为自己的发送邮箱地址；<br>如果发现邮件发送失败，检查DEBUG是否设置成False，smtp服务器的用户名和密码是否正确，是否设置了SERVER_EMAIL参数；<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><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">ADMINS = (</span><br><span class="line">    (<span class="string">'haku'</span>, <span class="string">'g@kohaku.cc'</span>),</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">MANAGERS = ADMINS</span><br><span class="line"></span><br><span class="line"><span class="comment"># Email</span></span><br><span class="line">EMAIL_BACKEND = <span class="string">'django.core.mail.backends.smtp.EmailBackend'</span></span><br><span class="line">EMAIL_USE_TLS = <span class="keyword">True</span></span><br><span class="line">EMAIL_HOST = local_settings.EMAIL_HOST</span><br><span class="line">EMAIL_PORT = local_settings.EMAIL_PORT</span><br><span class="line">EMAIL_HOST_USER = local_settings.EMAIL_HOST_USER</span><br><span class="line">EMAIL_HOST_PASSWORD = local_settings.EMAIL_HOST_PASSWORD</span><br><span class="line">DEFAULT_FROM_EMAIL = local_settings.DEFAULT_FROM_EMAIL</span><br><span class="line">SERVER_EMAIL = local_settings.DEFAULT_FROM_EMAIL</span><br></pre></td></tr></table></figure></p><p>为了检测配置是否正确，在本地运行Python 内置的SMTP 测试服务器:<br><code>python -m smtpd -n -c DebuggingServer localhost:1025</code></p><p>然后在settings.py 设置：<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">EMAIL_HOST=<span class="string">'localhost'</span></span><br><span class="line">EMAIL_PORT=<span class="number">1025</span></span><br></pre></td></tr></table></figure></p><p>触发一个500错误，或者生成一条error及以上级别的日志<code>logger.error(&#39;test&#39;)</code>，在终端上会显示发送的邮件内容；</p><h3 id="404-Error-Reporting"><a href="#404-Error-Reporting" class="headerlink" title="404 Error Reporting"></a>404 Error Reporting</h3><p>当DEBUG设置成False，并且在MIDDLEWARE设置中包含了django.middleware.common.BrokenLinkEmailsMiddleware，Django就会在抛出404异常并且该请求头<strong>包含referer</strong> 的时候给MANAGERS 列表中的用户发送邮件，这样做既能检测Broken Links，又能避免因为用户手动输入一个无效的地址而报错；</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Django是一个用python写的轻量级Web框架，虽说是轻量级，但是它能做的事情并不少，有了Django我们就可以用很少的代码来搭建一个现代化的网站，避免重复造轮子。Django2.0之后已经放弃了对python2 的支持，只支持python3.4 及以上版本。&lt;/p&gt;
&lt;p&gt;Django框架包含了一个完整Web应用各方面的知识点，下面对Django一些常用的特性做一个梳理，旨在学习Django的架构和源码。&lt;/p&gt;
    
    </summary>
    
    
      <category term="programming" scheme="http://b.kohaku.cc/tags/programming/"/>
    
      <category term="python" scheme="http://b.kohaku.cc/tags/python/"/>
    
      <category term="django" scheme="http://b.kohaku.cc/tags/django/"/>
    
  </entry>
  
  <entry>
    <title>写在2018的开始</title>
    <link href="http://b.kohaku.cc/2018/01/08/new-year-2018/"/>
    <id>http://b.kohaku.cc/2018/01/08/new-year-2018/</id>
    <published>2018-01-08T02:56:09.000Z</published>
    <updated>2018-01-17T02:57:14.519Z</updated>
    
    <content type="html"><![CDATA[<p>没想到啊没想到，2018年刚刚开始，就被一场重感冒当头一棒，整整一个星期才慢慢缓过来。</p><p>回顾2017年，也不知道做了些什么事。读完一遍Stavrianos 的《全球通史》，重读了一次余华的《活着》，读了东野圭吾的畅销书《解忧杂货店》，还有一些看了就像没看一样的。读了一些Django 的源码，入门了Node，假装给开源世界做了一点贡献，花了两天时间给kohaku.cc 搬家。2017年哪里都没有去……</p><p>惊慌地想要在日记中再找一些风和阳光的蛛丝马迹，聊以慰藉，失望地发现2017年5月某日的上一篇是16年的5月，突然间悲从中来。</p><p>2017年，我没有在世界上留下痕迹，岁月却在我身上留下痕迹，长丑了，都说相由心生。</p><p>那么2018年，我有一个愿望，就是变帅！</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;没想到啊没想到，2018年刚刚开始，就被一场重感冒当头一棒，整整一个星期才慢慢缓过来。&lt;/p&gt;
&lt;p&gt;回顾2017年，也不知道做了些什么事。读完一遍Stavrianos 的《全球通史》，重读了一次余华的《活着》，读了东野圭吾的畅销书《解忧杂货店》，还有一些看了就像没看一样的
      
    
    </summary>
    
    
      <category term="不知所云" scheme="http://b.kohaku.cc/tags/%E4%B8%8D%E7%9F%A5%E6%89%80%E4%BA%91/"/>
    
      <category term="2018" scheme="http://b.kohaku.cc/tags/2018/"/>
    
  </entry>
  
  <entry>
    <title>任务计划与windows服务的集群化</title>
    <link href="http://b.kohaku.cc/2017/12/26/clustered-tasks-and-services/"/>
    <id>http://b.kohaku.cc/2017/12/26/clustered-tasks-and-services/</id>
    <published>2017-12-26T09:30:53.000Z</published>
    <updated>2017-12-27T00:09:14.099Z</updated>
    
    <content type="html"><![CDATA[<p>我们常常会使用任务计划定时执行一些日常任务，若需要程序长时间执行又不需要用户登录，可能还会用到Windows Service，一般情况单台服务器就能满足要求，但对一些要求比较高的任务可能需要集群化来保证高可用性。在故障转移集群上实现任务计划的集群化和Windows Service的集群化有一些差异。</p><a id="more"></a><h3 id="任务计划集群化"><a href="#任务计划集群化" class="headerlink" title="任务计划集群化"></a>任务计划集群化</h3><p>Windows Server 2012 之前，我们可以在集群的节点上部署任务，但是任务计划程序对整个集群是不感知的，手动管理各个节点上的任务计划比较容易出错，尤其是在节点多的情况下，Windows Server 2012 以后，通过Cluster Scheduled Tasks 可以十分简单地通过PowerShell 来管理集群上的任务计划，只需要在任意一个节点上注册任务。比较常用的是AnyNode 模式，任务计划在任意一个节点上启用，在其他节点都处于禁用状态。</p><p>对于已存在的任务计划，可以先导出xml，以该文件名作为参数进行任务注册，这样就不用单独配置触发器了：<br><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">Register-ClusteredScheduledTask -TaskName MyTask -Cluster MyCluster -Xml <span class="variable">$xmlFile</span></span><br></pre></td></tr></table></figure></p><blockquote><p>注意： 使用-Xml参数的时候需要保证任务计划用户在各个节点上都存在</p></blockquote><p>详见 <a href="https://blogs.msdn.microsoft.com/clustering/2012/05/31/how-to-configure-clustered-tasks-with-windows-server-2012/" target="_blank" rel="noopener">How to Configure Clustered Tasks with Windows Server 2012</a></p><h3 id="Windows-service集群化"><a href="#Windows-service集群化" class="headerlink" title="Windows service集群化"></a>Windows service集群化</h3><p>Windows Service 不能感知到整个集群，因此Windows Service集群化需要首先在各个节点上安装服务，然后再在故障转移集群管理器中添加服务。</p><p>参考 <a href="https://www.1e.com/blogs/2014/11/17/creating-a-windows-cluster-part-5-adding-applications-and-services-to-the-cluster/" target="_blank" rel="noopener">Creating a Windows Cluster: Part 5 – Adding Applications and Services to the Cluster</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;我们常常会使用任务计划定时执行一些日常任务，若需要程序长时间执行又不需要用户登录，可能还会用到Windows Service，一般情况单台服务器就能满足要求，但对一些要求比较高的任务可能需要集群化来保证高可用性。在故障转移集群上实现任务计划的集群化和Windows Service的集群化有一些差异。&lt;/p&gt;
    
    </summary>
    
    
      <category term="scheduled task" scheme="http://b.kohaku.cc/tags/scheduled-task/"/>
    
      <category term="Windows service" scheme="http://b.kohaku.cc/tags/Windows-service/"/>
    
      <category term="clustered" scheme="http://b.kohaku.cc/tags/clustered/"/>
    
  </entry>
  
  <entry>
    <title>搬家啦~</title>
    <link href="http://b.kohaku.cc/2017/12/23/move-to-hexo/"/>
    <id>http://b.kohaku.cc/2017/12/23/move-to-hexo/</id>
    <published>2017-12-23T07:39:03.000Z</published>
    <updated>2017-12-23T11:10:22.406Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>君子生非异也，善假于物也</p></blockquote><p>之前对 <code>python</code> 兴趣正浓的时候用 <code>django</code> 写过一个博客，一边写一边读源码，颇有收获，奈何缺少艺术天分，不懂设计，没有风格，难看得很，一开始写点东西的欲望逐渐消磨，小站也渐渐疏于维护。</p><p>偶然发现朋友用hexo 搭的博客，简洁明了，于是萌生了重新梳理博客的想法，纠结于用hexo 搭一个和重写原来的博客之间，最后还是选择 <a href="https://hexo.io/" target="_blank" rel="noopener">hexo</a>，发现竟意外地好用，于是就有了现在的模样。原来的 kohaku.cc 暂时运行在另一个次元，将来可能以新的面目出现。</p><a id="more"></a><p>最近的日记本写到最后一页，又懒得去买新本子，就这么空了几个月。今后将在此不定期更新，记录一些或好或坏心情，以及其他…… </p><img src="/2017/12/23/move-to-hexo/old-man.jpg">]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;君子生非异也，善假于物也&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;之前对 &lt;code&gt;python&lt;/code&gt; 兴趣正浓的时候用 &lt;code&gt;django&lt;/code&gt; 写过一个博客，一边写一边读源码，颇有收获，奈何缺少艺术天分，不懂设计，没有风格，难看得很，一开始写点东西的欲望逐渐消磨，小站也渐渐疏于维护。&lt;/p&gt;
&lt;p&gt;偶然发现朋友用hexo 搭的博客，简洁明了，于是萌生了重新梳理博客的想法，纠结于用hexo 搭一个和重写原来的博客之间，最后还是选择 &lt;a href=&quot;https://hexo.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;hexo&lt;/a&gt;，发现竟意外地好用，于是就有了现在的模样。原来的 kohaku.cc 暂时运行在另一个次元，将来可能以新的面目出现。&lt;/p&gt;
    
    </summary>
    
      <category term="Life" scheme="http://b.kohaku.cc/categories/Life/"/>
    
    
      <category term="hexo" scheme="http://b.kohaku.cc/tags/hexo/"/>
    
  </entry>
  
  <entry>
    <title>Python 弱引用</title>
    <link href="http://b.kohaku.cc/2017/05/23/python-weakref/"/>
    <id>http://b.kohaku.cc/2017/05/23/python-weakref/</id>
    <published>2017-05-23T01:35:34.000Z</published>
    <updated>2018-03-13T01:16:18.716Z</updated>
    
    <content type="html"><![CDATA[<p>Python 的 <code><a href="https://docs.python.org/2/library/weakref.html" target="_blank" rel="noopener">weakref</a></code> 模块实现对对象的弱引用（Weak References）。我们知道，Python 的垃圾回收机制基于引用计数器实现，实例创建后将由引用计数器管理，对实例的每一次引用都会使其引用计数器加1，引用的删除使引用计数器减1，如果引用计数器到达0实例将被销毁。但是有时候我们希望在内存需要的时候更早地销毁一个对象，比如发生循环引用时，或者在建立一个对象缓存时；弱引用不能保证对象不被销毁，当一个对象只存在弱引用时，它也会被销毁。</p><a id="more"></a><p>弱引用的一个基本用法就是实现缓存和大对象映射，使一个大对象不会因为存在于缓存或者一个键值对中而逃避被销毁。例如，我们有一些大的二进制图片对象，现在打算把他们和名称一一对应，如果使用 Python 的 <code>dictionary</code> 将名称映射到图片对象（或者反过来），图片对象会一直存在于内存中，因为它们依然作为字典的值（或者键）存在，弱引用可以解决这个问题，使用基于<code>weakref</code>模块实现的 <code>WeakKeyDictionary</code> 和 <code>WeakValueDictionary</code> ,用弱引用来构造映射关系，我们假设图片对象是 <code>WeakValueDictionary</code> 的值，当该对象的引用只是 weak mapping 中的弱引用时，GC会回收该对象，weak mapping 中对应的条目也会被删除；一些内建类型比如 <code>list</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><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> sys</span><br><span class="line"><span class="keyword">import</span> weakref</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">A</span><span class="params">(object)</span>:</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">a = A()</span><br><span class="line"><span class="keyword">print</span> sys.getrefcount(a)            <span class="comment"># 2</span></span><br><span class="line"></span><br><span class="line">b = a</span><br><span class="line"><span class="keyword">print</span> sys.getrefcount(a)            <span class="comment"># 3</span></span><br><span class="line"></span><br><span class="line">c = weakref.ref(a)</span><br><span class="line"><span class="keyword">print</span> sys.getrefcount(a)            <span class="comment"># 3</span></span><br><span class="line"><span class="keyword">print</span> weakref.getweakrefcount(a)    <span class="comment"># 1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">del</span> b</span><br><span class="line"><span class="keyword">print</span> sys.getrefcount(a)            <span class="comment"># 2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">del</span> c</span><br><span class="line"><span class="keyword">print</span> sys.getrefcount(a)            <span class="comment"># 2</span></span><br><span class="line"><span class="keyword">print</span> weakref.getweakrefcount(a)    <span class="comment"># 0</span></span><br></pre></td></tr></table></figure><p>从上面的例子可以看到，对对象的弱引用使用单独的计数器；之所以第一次获取引用计数结果是 <code>2</code>而不是 <code>1</code>，是因为把 <code>a</code> 当参数传入了 <code>getrefcount()</code> 方法。</p><p>总结，弱引用主要解决垃圾回收的问题，多用在处理对象缓存和循环引用问题上；回顾一下弱引用在 <code>django</code> 中的一个使用场景，<code>Signal</code> 类初始化的时候就定义了一个 <code>receiver</code> 缓存，在连接<code>receiver</code> 和信号的时候，默认对 <code>receiver</code> 使用弱引用；<code>weakref</code> 模块的方法和属性详见 <a href="https://docs.python.org/2/library/weakref.html" target="_blank" rel="noopener">weakref</a></p><p>相关文档：<a href="https://pymotw.com/2/weakref/index.html" target="_blank" rel="noopener">https://pymotw.com/2/weakref/index.html</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Python 的 &lt;code&gt;&lt;a href=&quot;https://docs.python.org/2/library/weakref.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;weakref&lt;/a&gt;&lt;/code&gt; 模块实现对对象的弱引用（Weak References）。我们知道，Python 的垃圾回收机制基于引用计数器实现，实例创建后将由引用计数器管理，对实例的每一次引用都会使其引用计数器加1，引用的删除使引用计数器减1，如果引用计数器到达0实例将被销毁。但是有时候我们希望在内存需要的时候更早地销毁一个对象，比如发生循环引用时，或者在建立一个对象缓存时；弱引用不能保证对象不被销毁，当一个对象只存在弱引用时，它也会被销毁。&lt;/p&gt;
    
    </summary>
    
      <category term="Python" scheme="http://b.kohaku.cc/categories/Python/"/>
    
    
      <category term="Python" scheme="http://b.kohaku.cc/tags/Python/"/>
    
      <category term="Weakref" scheme="http://b.kohaku.cc/tags/Weakref/"/>
    
  </entry>
  
  <entry>
    <title>No no no</title>
    <link href="http://b.kohaku.cc/2014/09/27/no-no-no/"/>
    <id>http://b.kohaku.cc/2014/09/27/no-no-no/</id>
    <published>2014-09-27T04:18:47.000Z</published>
    <updated>2017-12-23T05:52:49.309Z</updated>
    
    <content type="html"><![CDATA[<p>Don’t anybody come</p><p>惊扰我的幻光</p><a id="more"></a><p>当我需要在这个时间掏出钥匙</p><p>这扇门 就是一道梦幻的分隔</p><p>窗台上的植物 </p><p>分明一动不动</p><p>又分明在变化</p><p>像靠在墙角的脑袋圆圆的娃娃</p><p>恍恍惚惚</p><p>置身于幻光</p><p>在心里生出一道意识</p><p>别碰它 一碰就碎</p><p>我也像那盆花</p><p>一动也不动</p><p>我们都小心翼翼啊</p><p>假装不得动弹</p><p>Don’t anybody come</p><p>惊扰我的幻光</p><p>只看着它</p><p>渐变昏黄</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Don’t anybody come&lt;/p&gt;
&lt;p&gt;惊扰我的幻光&lt;/p&gt;
    
    </summary>
    
      <category term="Life" scheme="http://b.kohaku.cc/categories/Life/"/>
    
    
      <category term="不知所云" scheme="http://b.kohaku.cc/tags/%E4%B8%8D%E7%9F%A5%E6%89%80%E4%BA%91/"/>
    
  </entry>
  
</feed>
