wecatch
2016-11-20T16:28:54.000Z
http://wecatch.me/
wecatch
Hexo
如何理解 python UnicodeEncodeError :python 的 string 和 unicode
http://wecatch.me/2016/11/06/python-string-unicode/
2016-11-06T12:48:08.000Z
2016-11-20T16:28:54.000Z
<p><a href="http://sanyuesha.com/about/" target="_blank" rel="external">三月沙</a> <a href="http://sanyuesha.com/2016/11/06/python-string-unicode/" target="_blank" rel="external">原文链接</a></p>
<blockquote>
<p>文中 python 皆为 2.x 版本</p>
</blockquote>
<p>初学 python 的人基本上都有过如下类似经历:</p>
<p><strong>UnicodeDecodeError</strong><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">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"<input>"</span>, line <span class="number">1</span>, <span class="keyword">in</span> <module></span><br><span class="line">UnicodeDecodeError: <span class="string">'ascii'</span> codec can<span class="string">'t decode byte 0xe4 in position 0: ordinal not in range(128)</span></span><br></pre></td></tr></table></figure></p>
<p><strong>UnicodeEncodeError</strong><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">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"<input>"</span>, line <span class="number">1</span>, <span class="keyword">in</span> <module></span><br><span class="line">UnicodeEncodeError: <span class="string">'ascii'</span> codec can<span class="string">'t encode characters in position 0-1: ordinal not in range(128)</span></span><br></pre></td></tr></table></figure></p>
<p>这两个错误在 python 中十分常见,一不留神就碰上了。如果你写过c、c++ 或者 java,对比之下一定会觉得 python 这个错误真让人火大。事实也确实如此,我也曾经很火大🔥。</p>
<p>这两个错误究竟意味着什么?可以先从 python 的基本数据类型 string 和 unicode 开始。</p>
<h1 id="string"><a href="#string" class="headerlink" title="string"></a>string</h1><p>字符串(string)其实就是一段文本序列,是由一个或多个字符组成(character),字符是文本的最小构成单元,在 python 中可以用以下方式表示字符串:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">>>> s1 = <span class="string">'abc'</span></span><br><span class="line">>>> s2 = <span class="string">"abc"</span></span><br><span class="line">>>> s3 = <span class="string">""</span><span class="string">"</span><br><span class="line"> abc</span><br><span class="line"> "</span><span class="string">""</span></span><br><span class="line">>>> s4 = <span class="string">'中文'</span></span><br><span class="line">>>> <span class="keyword">for</span> i <span class="keyword">in</span> [s1, s2, s3, s4]:</span><br><span class="line"> <span class="built_in">print</span> <span class="built_in">type</span>(i)</span><br><span class="line"><<span class="built_in">type</span> <span class="string">'str'</span>></span><br><span class="line"><<span class="built_in">type</span> <span class="string">'str'</span>></span><br><span class="line"><<span class="built_in">type</span> <span class="string">'str'</span>></span><br><span class="line"><<span class="built_in">type</span> <span class="string">'str'</span>></span><br></pre></td></tr></table></figure>
<p>这些变量在 python shell 中对应输出是:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">s1 --> <span class="string">'abc'</span></span><br><span class="line">s2 --> <span class="string">'abc'</span></span><br><span class="line">s3 --> <span class="string">'\nabc\n'</span></span><br><span class="line">s4 --> <span class="string">'\xe4\xb8\xad\xe6\x96\x87'</span></span><br></pre></td></tr></table></figure>
<p>s4 的输出和其它变量明显不同,字面上是一个 16 进制序列,但是 s4 和其它字符串一样,在 python 内部都是用同样方式进行存储的: 字节流(byte stream),即字节序列。</p>
<p>字节是计算机内部最小的可寻址的存储单位(对大部分计算机而言),一个字节是由 8 bit 组成,也就是对应 8 个二进制位。其实可以更进一步解释说,python 不仅用字节的方式存储着变量中的字符串文本,python 文件中的所有信息在计算机内部都是用一个个字节表示的,计算机是用这样的方式存储文本数据的。</p>
<h2 id="字符串用字节如何表示?"><a href="#字符串用字节如何表示?" class="headerlink" title="字符串用字节如何表示?"></a>字符串用字节如何表示?</h2><p>答案就是<strong>编码</strong>。计算机是只能识别 0 或 1 这样的二进制信息,而不是 a 或 b 这样对人类有意义的字符,为了让机器能读懂这些字符,人类就发明字符到二进制的映射关系,然后按照这个映射规则进行相应地编码。<a href="https://en.wikipedia.org/wiki/ascii" target="_blank" rel="external">ascii</a> 就是这样背景下诞生的一种编码规则。<strong>ascii 也是 python 2.x 默认使用的编码规则。</strong></p>
<p>ascii 规定了常用的字符到计算机是如何映射的,编码范围是 0~127 共 128 个字符。简单来说它就是一本字典,规定了不同字符的对应的编码值(code point,一个整数值),这样一来计算机就能用二进制表示了。比如字符 a 的编码是 97,对应的二进制是 1100001,<strong>一个字节</strong>就足够存储这些信息。字符串 “abc” 最终存储就是 <code>[97] [98] [99]</code> 三个字节。python 默认情况下就是使用这个规则对字符进行编码,对字节进行解码(反编码)。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>ord(<span class="string">'a'</span>)</span><br><span class="line"><span class="number">97</span></span><br><span class="line"><span class="meta">>>> </span>chr(<span class="number">97</span>)</span><br><span class="line"><span class="string">'a'</span></span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<p>由于 ascii 的编码范围非常有限,对超过 ascii 范围之外的字符,python 是如何处理的?很简单,抛错误出来,这就是 <code>UnicodeEncodeError</code> 和 <code>UnicodeDecodeError</code> 的来源。那 python 会在什么时候抛出这样的错误,也就是说 python 进行编码和解码的操作发生在何时?</p>
<h1 id="unicode-对象"><a href="#unicode-对象" class="headerlink" title="unicode 对象"></a>unicode 对象</h1><p>unicode 对象和 string 一样,是 python 中的一种字符对象(python 中一切皆对象,string 也是)。先不要去想 unicode 字符集、unicode 编码或者 utf-8 这些概念,在此特意加了<code>对象</code>就是为了和后面提到的 unicode 字符集进行区分。这里说的 unicode 就是 python 中的 unicode 对象,构造函数是 <code>unicode()</code>。</p>
<p>在 python 中创造 unicode 对象也很简单:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>s1 = unicode(<span class="string">'abc'</span>)</span><br><span class="line"><span class="meta">>>> </span>s2 = <span class="string">u'abc'</span></span><br><span class="line"><span class="meta">>>> </span>s3 = U<span class="string">'abc'</span></span><br><span class="line"><span class="meta">>>> </span>s4 = <span class="string">u'中文'</span></span><br></pre></td></tr></table></figure>
<p>这些变量在 python shell 中对应输出是:<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></pre></td><td class="code"><pre><span class="line">s1 --> u<span class="string">'abc'</span></span><br><span class="line">s2 --> u<span class="string">'abc'</span></span><br><span class="line">s3 --> u<span class="string">'abc'</span></span><br><span class="line">s4 --> u<span class="string">'\u4e2d\u6587'</span></span><br></pre></td></tr></table></figure></p>
<p>同样的,s4 的输出和其它变量不同,这些就是unicode 字符。由于 ascii 能够表示的字符太少,而且不够通用(扩展 ascii 的话题,就是把 ascii 没有利用的剩下大于 127 的位置利用了,在不同的字符集里代表不同的意思),<a href="https://zh.wikibooks.org/wiki/Unicode" target="_blank" rel="external">unicode 字符集</a> 就被造出来了,一本更大的字典,里面有更多的编码值。</p>
<h2 id="unicode-字符集"><a href="#unicode-字符集" class="headerlink" title="unicode 字符集"></a>unicode 字符集</h2><p>unicode 字符集解决了:</p>
<ul>
<li>ascii 表达能力不够的问题</li>
<li>扩展 ascii 不够通用的问题</li>
</ul>
<p>虽然 unicode 字符集表达能力强,又能够统一字符编码规则,但是它并没有规定这些字符在计算机中是如何表示的。它和 ascii 不同,很多字符(编码值大于 255 )没有办法用一个字节就搞定。怎样做到高效快捷地存储这些编码值?于是就有了 unicode 字符集的编码规则的实现:utf-8、utf-16等。</p>
<p>到这里可以简单理清 ascii、unicode 字符集、utf-8等的关系了:ascii 和 unicode 字符集都是一种编码集,由字符和字符对应的整数值(code point)组成,ascii 在计算机内部用一个字节存储,utf-8 是 unicode 字符集存储的具体实现,因为 unicode 字符集没有办法简简单单用一个字节搞定。</p>
<p>回到 s4 对应的输出,这个输出就是 unicode 字符集对应的编码值(code point)的 16 进制表示。</p>
<p>unicode 对象是用来表示 unicode 字符集中的字符,这些字符(实际是那个编码值,一个整数) 在 python 中又是如何存储的?有了前文的分析,也许可以猜到,python 依然是通过编码然后用字节的方式存储,但是这里的编码就不能是 ascii 了,而是对应 unicode 字符集的编码规则: utf-8、utf-16等。</p>
<h2 id="unicode-对象的编码"><a href="#unicode-对象的编码" class="headerlink" title="unicode 对象的编码"></a>unicode 对象的编码</h2><p>unicode 对象想要正确的存储就必须指定相应的编码规则,这里我们只讨论使用最广泛的 utf-8 实现。</p>
<p>在 python 中对 unicode 对象编码如下:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>s=<span class="string">u'中文'</span></span><br><span class="line"><span class="meta">>>> </span>s.encode(<span class="string">'utf-8'</span>)</span><br><span class="line"><span class="string">'\xe4\xb8\xad\xe6\x96\x87'</span></span><br><span class="line"><span class="meta">>>> </span>type(s.encode(<span class="string">'utf-8'</span>))</span><br><span class="line"><type <span class="string">'str'</span>></span><br></pre></td></tr></table></figure>
<p>编码之后输出的是个 string 并以字节序列的方式进行存储。有了编码就会有解码,python 正是在这种编码、解码的过程使用了错误的编码规则而发生了 <code>UnicodeEncodeError</code> 和 <code>UnicodeDecodeError</code> 错误,因为它默认使用 ascii 来完成转换。</p>
<h1 id="string-和-unicode-对象的转换"><a href="#string-和-unicode-对象的转换" class="headerlink" title="string 和 unicode 对象的转换"></a>string 和 unicode 对象的转换</h1><p>unicode 对象可以用 utf-8 编码为 string,同理,string 也可以用 utf-8 解码为 unicode 对象</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">>>> </span>u=<span class="string">u'中文'</span></span><br><span class="line"><span class="meta">>>> </span>s = u.encode(<span class="string">'utf-8'</span>)</span><br><span class="line"><span class="meta">>>> </span>s</span><br><span class="line"><span class="string">'\xe4\xb8\xad\xe6\x96\x87'</span></span><br><span class="line"><span class="meta">>>> </span>type(s)</span><br><span class="line"><type <span class="string">'str'</span>></span><br><span class="line"><span class="meta">>>> </span>s.decode(<span class="string">'utf-8'</span>)</span><br><span class="line"><span class="string">u'\u4e2d\u6587'</span></span><br><span class="line"><span class="meta">>>> </span>type(s.decode(<span class="string">'utf-8'</span>))</span><br><span class="line"><type <span class="string">'unicode'</span>></span><br></pre></td></tr></table></figure>
<p>错误的编码规则就会导致那两个常见的异常</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">>>> u.encode('ascii')</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File "<input>", line 1, in <module></span><br><span class="line">UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)</span><br><span class="line">>>></span><br><span class="line">>>> s.decode('ascii')</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File "<input>", line 1, in <module></span><br><span class="line">UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)</span><br></pre></td></tr></table></figure>
<p>这两个错误在某些时候会突然莫名其妙地出现就是因为 python 自动地使用了 ascii 编码。</p>
<h2 id="python-自动解编码"><a href="#python-自动解编码" class="headerlink" title="python 自动解编码"></a>python 自动解编码</h2><p>1.stirng 和 unicode 对象合并</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">>>> s + u''</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File "<input>", line 1, in <module></span><br><span class="line">UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)</span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<p>2.列表合并</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>as_list = [u, s]</span><br><span class="line"><span class="meta">>>> </span><span class="string">''</span>.join(as_list)</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"<input>"</span>, line <span class="number">1</span>, <span class="keyword">in</span> <module></span><br><span class="line">UnicodeDecodeError: <span class="string">'ascii'</span> codec can<span class="string">'t decode byte 0xe4 in position 0: ordinal not in range(128)</span></span><br></pre></td></tr></table></figure>
<p>3.格式化字符串</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">>>> <span class="string">'%s-%s'</span>%(s,u)</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"<input>"</span>, line 1, <span class="keyword">in</span> <module></span><br><span class="line">UnicodeDecodeError: <span class="string">'ascii'</span> codec can<span class="string">'t decode byte 0xe4 in position 0: ordinal not in range(128)</span><br><span class="line">>>></span></span><br></pre></td></tr></table></figure>
<p>4.打印 unicode 对象</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="comment">#test.py</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line">u = <span class="string">u'中文'</span></span><br><span class="line"><span class="keyword">print</span> u</span><br><span class="line"></span><br><span class="line"><span class="comment">#outpt</span></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"/Users/zhyq0826/workspace/zhyq0826/blog-code/p20161030_python_encoding/uni.py"</span>, line <span class="number">3</span>, <span class="keyword">in</span> <module></span><br><span class="line"> <span class="keyword">print</span> u</span><br><span class="line">UnicodeEncodeError: <span class="string">'ascii'</span> codec can<span class="string">'t encode characters in position 0-1: ordinal not in range(128)</span></span><br></pre></td></tr></table></figure>
<p>5.输出到文件</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">>>> f = open('text.txt','w')</span><br><span class="line">>>> f.write(u)</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File "<input>", line 1, in <module></span><br><span class="line">UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)</span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<hr>
<p>1,2,3 的例子中,python 自动用 ascii 把 string 解码为 unicode 对象然后再进行相应操作,所以都是 <code>decode</code> 错误, 4 和 5 python 自动用 ascii 把 unicode 对象编码为字符串然后输出,所以都是 <code>encode</code> 错误。</p>
<p>只要涉及到 unicode 对象和 string 的转换以及 unicode 对象输出、输入的地方可能都会触发 python 自动进行解码/编码,比如写入数据库、写入到文件、读取 socket 等等。</p>
<p>到此,这两个异常产生的真正原因了基本已经清楚了: unicode 对象需要<strong>编码</strong>为相应的 string(字符串)才可以存储、传输、打印,字符串需要<strong>解码</strong>为对应的 unicode 对象才能完成 unicode 对象的各种操作,<code>len</code>、<code>find</code> 等。</p>
<figure class="highlight"><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">string.decode('utf-8') --> unicode</span><br><span class="line">unicode.encode('utf-8') --> string</span><br></pre></td></tr></table></figure>
<h1 id="如何避免这些的错误"><a href="#如何避免这些的错误" class="headerlink" title="如何避免这些的错误"></a>如何避免这些的错误</h1><p>1.理解编码或解码的转换方向</p>
<p>无论何时发生编码错误,首先要理解编码方向,然后再针对性解决。</p>
<p>2.设置默认编码为 utf-8</p>
<p>在文件头写入 </p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br></pre></td></tr></table></figure>
<p>python 会查找: coding: name or coding=name,并设置文件编码格式为 <code>name</code>,此方式是告诉 python 默认编码不再是 ascii ,而是要使用声明的编码格式。</p>
<p>3.输入对象尽早解码为 unicode,输出对象尽早编码为字节流</p>
<p>无论何时有字节流输入,都需要尽早解码为 unicode 对象。任何时候想要把 unicode 对象写入到文件、数据库、socket 等外界程序,都需要进行编码。</p>
<p>4.使用 codecs 模块来处理输入输出 unicode 对象</p>
<p><a href="https://docs.python.org/2/library/codecs.html" target="_blank" rel="external">codecs</a> 模块可以自动的完成解编码的工作。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span><span class="keyword">import</span> codecs</span><br><span class="line"><span class="meta">>>> </span>f = codecs.open(<span class="string">'text.txt'</span>, <span class="string">'w'</span>, <span class="string">'utf-8'</span>)</span><br><span class="line"><span class="meta">>>> </span>f.write(u)</span><br><span class="line"><span class="meta">>>> </span>f.close()</span><br></pre></td></tr></table></figure>
<blockquote>
<p>参考文献</p>
<ul>
<li><a href="https://zh.wikibooks.org/wiki/Unicode" target="_blank" rel="external">https://zh.wikibooks.org/wiki/Unicode</a></li>
<li><a href="https://zh.wikibooks.org/wiki/ascii" target="_blank" rel="external">https://zh.wikibooks.org/wiki/ascii</a></li>
<li><a href="http://www.unicode.org/" target="_blank" rel="external">http://www.unicode.org/</a></li>
<li><a href="https://docs.python.org/2/howto/unicode.html" target="_blank" rel="external">https://docs.python.org/2/howto/unicode.html</a></li>
<li><a href="http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html" target="_blank" rel="external">http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html</a></li>
</ul>
<p>注意:转载请注明出处和文章链接</p>
</blockquote>
<p><a href="http://sanyuesha.com/about/" target="_blank" rel="external">三月沙</a> <a href="http://sanyuesha.com/2016/11/0
记一次 mongodb 单表亿级数据的拆分方案
http://wecatch.me/2016/08/27/mongodb-single-table-split/
2016-08-27T13:38:14.000Z
2016-08-27T14:14:21.000Z
<p><a href="http://sanyuesha.com/about/" target="_blank" rel="external">三月沙</a> <a href="http://sanyuesha.com/2016/08/27/mongodb-single-table-split/" target="_blank" rel="external">原文链接</a></p>
<p>拆表是一种常见的解决单表数据库瓶颈的方案,在实际的应用场景中能够部分解决单表的写压力和读压力,但是也会带来一些更复杂的影响:</p>
<ul>
<li>聚合查询变得困难</li>
<li>拆分的键一旦选定,更改会非常困难</li>
<li>拆表的过程要保证线上业务不受影响,操作复杂度高</li>
</ul>
<p>因此,表的拆分一定要选在恰当的时候进行,过早,付出很大代价也并不会带来性能的提升,过晚,数据量庞大,操作难度加大。</p>
<p>在本次实施的拆分方案中,数据特点是:</p>
<ul>
<li>单表过亿</li>
<li>业务数据是用户对资源的收藏,结构比较单一</li>
<li>只做单向(用户–>数据)查找,不要求反向(数据–>用户)查找</li>
<li>数据处于实时变更状态(新增、删除、查询等操作并存)</li>
</ul>
<h2 id="如何选择拆分键"><a href="#如何选择拆分键" class="headerlink" title="如何选择拆分键"></a>如何选择拆分键</h2><p>本例中,拆分的键就是用户 id,且每个用户关联的资源数据最大不会过万。</p>
<h2 id="如何保证拆分的过程中不影响用户的操作?"><a href="#如何保证拆分的过程中不影响用户的操作?" class="headerlink" title="如何保证拆分的过程中不影响用户的操作?"></a>如何保证拆分的过程中不影响用户的操作?</h2><p>本例中,表中的数据基本上只有如下操作</p>
<ul>
<li>用户新增一条数据</li>
<li>用户删除一条数据</li>
<li>用户查询数据(有翻页)</li>
</ul>
<a id="more"></a>
<p>拆表的基本原理就是对选择的键进行 hash 生成每个键所属的表空间名,也就是说,在任意时刻,任意用户的数据只有以下三种状态</p>
<p>1.全部在旧表中<br>2.部分在新表,部分在旧表<br>3.全部在新表中</p>
<p>数据拆分的具体工作是要离线进行的,为了能保证用户数据在这样三种状态之下依然具有像单表中一样的一致性,需要业务层在处理当前用户数据时判断用户是否在迁移中,只有处于迁移状态之下,用户数据才需要特别处理。</p>
<h3 id="迁移状态如何判断"><a href="#迁移状态如何判断" class="headerlink" title="迁移状态如何判断?"></a>迁移状态如何判断?</h3><p>如果当前用户数据处于迁移状态,为了保证用户数据全部可用,即要做到不跨表翻页,这里做了特殊处理:一旦开始迁移,用户的全部数据就会载入到一个特殊区域(这里我们用了 redis,后面再讨论这样处理可能带来的问题),且保存为有序集数据。</p>
<p>所以,如果用户处于迁移状态,则用户的数据一定存在于这个区域,暂且给此区域命名为 <em>on_progress</em>。</p>
<h3 id="迁移状态下如何保证用户数据的正确性?"><a href="#迁移状态下如何保证用户数据的正确性?" class="headerlink" title="迁移状态下如何保证用户数据的正确性?"></a>迁移状态下如何保证用户数据的正确性?</h3><p>当用户处于迁移状态,用户新增一条数据,则在新表和 <em>on_progress</em> 中各写入新增数据,用户删除一条数据,则在新表、<em>on_progress</em>同时删除该数据。</p>
<p><strong>这样做的目的是什么?</strong></p>
<p>处于迁移状态下的用户,<em>on_progress</em> 中保存了其所有数据,可以进行翻页操作,<em>on_progress</em> 中的所有数据会在离线状态下不断写入新表中,迁移完成之后,旧表中的数据将被删除,<em>on_progress</em> 中数据也将被清除,这样用户数据全部进入新表中,后续的所有操作将只在新表上进行。</p>
<p><em>on_progress</em> 中的数据在不断写入新表的过程中,是按照由新–>旧或由旧–>新的顺序进行,在这个过程中:</p>
<blockquote>
<p>用户删除一条数据</p>
</blockquote>
<p>1)有可能该条数据已经写入新表中,所以删除操作要在新表中执行。<br>2)<em>on_progress</em> 中的数据要始终和用户所有的操作同步来保证数据的正确性和一致性,因而删除操作也要在 <em>on_progress</em> 中执行。</p>
<blockquote>
<p>用户新增一条数据</p>
</blockquote>
<p>1)<em>on_progress</em> 中的数据要始终和用户所有的操作同步来保证数据的正确性和一致性,因而 <em>on_progress</em> 需要写入一条数据。<br>2)在某些边界条件下,<em>on_progress</em> (假如数据由旧–>新写入新表) 中的数据迁移完成但还未被删除,用户恰巧写入一条数据,此时用户仍处于迁移状态中,但是离线的迁移操作已经认为迁移停止了,因而此时新增数据需要进入新表才能保证数据最终是正确的。</p>
<h3 id="on-progress-的选择"><a href="#on-progress-的选择" class="headerlink" title="on_progress 的选择"></a>on_progress 的选择</h3><p>在这个方案的实施中,<em>on_progress</em> 用了 redis 的有序集,键就是用户的 id,只要检测到用户 id 的存在就认为该用户处于迁移状态中。</p>
<p>迁移完成之后,先删除旧表中的数据,然后再删除 <em>on_progress</em> 中有序集,所以,只要用户处于 redis 中,则此时一定是迁移中或迁移完成,如果在旧表中找到用户数据,则该用户一定还没有开始迁移,否则用户已经迁移成功。</p>
<p>需要注意的是,选用 redis 可能带来的问题是 redis 宕机会导致迁移中的数据无法回复(redis 未开持久化操作或其他原因导致数据不能恢复),只要用户在旧表中有数据存在,则用户删除或新增数据的操作一定也要在旧表中执行,再次恢复迁移时,要清除新表数据之后才能正常进行。</p>
<p><img src="http://wecatch.me/blog/images/table-split.png" alt="迁移流程图"></p>
<p><a href="http://sanyuesha.com/about/">三月沙</a> <a href="http://sanyuesha.com/2016/08/27/mongodb-single-table-split/">原文链接</a></p>
<p>拆表是一种常见的解决单表数据库瓶颈的方案,在实际的应用场景中能够部分解决单表的写压力和读压力,但是也会带来一些更复杂的影响:</p>
<ul>
<li>聚合查询变得困难</li>
<li>拆分的键一旦选定,更改会非常困难</li>
<li>拆表的过程要保证线上业务不受影响,操作复杂度高</li>
</ul>
<p>因此,表的拆分一定要选在恰当的时候进行,过早,付出很大代价也并不会带来性能的提升,过晚,数据量庞大,操作难度加大。</p>
<p>在本次实施的拆分方案中,数据特点是:</p>
<ul>
<li>单表过亿</li>
<li>业务数据是用户对资源的收藏,结构比较单一</li>
<li>只做单向(用户–>数据)查找,不要求反向(数据–>用户)查找</li>
<li>数据处于实时变更状态(新增、删除、查询等操作并存)</li>
</ul>
<h2 id="如何选择拆分键"><a href="#如何选择拆分键" class="headerlink" title="如何选择拆分键"></a>如何选择拆分键</h2><p>本例中,拆分的键就是用户 id,且每个用户关联的资源数据最大不会过万。</p>
<h2 id="如何保证拆分的过程中不影响用户的操作?"><a href="#如何保证拆分的过程中不影响用户的操作?" class="headerlink" title="如何保证拆分的过程中不影响用户的操作?"></a>如何保证拆分的过程中不影响用户的操作?</h2><p>本例中,表中的数据基本上只有如下操作</p>
<ul>
<li>用户新增一条数据</li>
<li>用户删除一条数据</li>
<li>用户查询数据(有翻页)</li>
</ul>
如何用 redis 造一把分布式锁
http://wecatch.me/2016/08/20/distributed-lock-with-redis/
2016-08-20T04:37:42.000Z
2016-08-23T13:50:39.000Z
<p><a href="http://sanyuesha.com/about/" target="_blank" rel="external">三月沙</a> <a href="http://sanyuesha.com/2016/08/20/distributed-lock-with-redis/" target="_blank" rel="external">原文链接</a></p>
<h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p><strong>锁</strong></p>
<blockquote>
<p><a href="https://en.wikipedia.org/wiki/Lock_(computer_science" target="_blank" rel="external">wiki</a>:In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechanism for enforcing limits on access to a resource in an environment where there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy.<br>在计算机科学中,锁或互斥量是一种同步机制,用于在多线程执行环境中,强行限制对资源访问。锁常被用于同步并发控制。</p>
</blockquote>
<p>简单来讲,锁是用来控制多线程执行对资源的并发访问的。比如当一个资源只允许在任意时刻只有一个执行线程对其进行写操作,那当其他线程要访问资源时,就必须要检查该该资源上是否存在<code>写操作锁</code>,如果存在,必须要等待锁的释放并获得锁之后才能对资源进行访问。</p>
<p><strong>悲观锁</strong></p>
<p>悲观锁假设在一个完整事务发生的过程中,总是会有其他线程会更改所操作的资源,因此线程总是对资源加锁之后才会对其做更改。</p>
<p><strong>乐观锁</strong></p>
<p>乐观锁假设在一个完整事务发生的过程中,不一定会有其他线程会更改资源,一旦发现资源被更改,则停止当前事务回滚所有操作。</p>
<p><strong>分布式锁</strong></p>
<p>常见的锁有多线程锁、数据库事务中的行级锁、表级锁等,这些锁的特点都是发生在单一系统环境中,如果需要在不同的进程、不同机器和系统等分布式环境中控制对资源的访问,这时候我们需要一把分布式锁。</p>
<p>不言而喻,分布式锁就是为了解决分布式环境中对资源的访问限制而诞生的。</p>
<h2 id="如何设计一把分布式锁"><a href="#如何设计一把分布式锁" class="headerlink" title="如何设计一把分布式锁"></a>如何设计一把分布式锁</h2><a id="more"></a>
<p>我们用 redis 来实现这把分布式的锁,redis 速度快、支持事务、可持久化的特点非常适合创建分布式锁。</p>
<h3 id="分布式环境中如何消除网络延迟对锁获取的影响"><a href="#分布式环境中如何消除网络延迟对锁获取的影响" class="headerlink" title="分布式环境中如何消除网络延迟对锁获取的影响"></a>分布式环境中如何消除网络延迟对锁获取的影响</h3><p>锁,简单来说就是存于 redis 中一个唯一的 key。一般而言,redis 用 <code>set</code> 命令来完成一个 key 的设置(加锁),使用 <code>get</code> 命令获取 key 的信息(检查锁)。由于网络延迟的存在,简单的使用 <code>set</code> 和 <code>get</code> 命令可能会带来如下问题:</p>
<p>线程 A 检查锁是否存在(get)–>否–>加锁(set),在 A 发起加锁命令但是还没有加锁成功的时候,可能线程 B 已经完成了 <code>set</code> 操作,锁被 B 获得,但是 A 也发起了加锁请求,由于 <code>set</code> 命令并不检查 key 的存在,B 的锁很可能会被 A 的 <code>set</code> 操作破坏。</p>
<p>幸运的是,redis 提供了另一个命令 <code>setx</code> : 当指定的 key 不存在时,设置 key 的值为指定 value,如果存在,不做任何操作,成功则返回 1,失败则返回 0。也就是只要命令返回成功,线程就能正确获得锁,不需要再做类似 <code>get</code> 检查操作。</p>
<p>使用 <code>setx</code> 可以消除网络延迟对锁设置的影响。</p>
<h3 id="加锁的客户端发生-crash-导致锁不能被正确释放应该怎么处理?"><a href="#加锁的客户端发生-crash-导致锁不能被正确释放应该怎么处理?" class="headerlink" title="加锁的客户端发生 crash 导致锁不能被正确释放应该怎么处理?"></a>加锁的客户端发生 crash 导致锁不能被正确释放应该怎么处理?</h3><p>加锁成功并操作完成之后,就需要加锁线程对锁进行释放,以让出资源的控制权。释放锁,就是删除 redis 中这个唯一的 key,但是一定要保证删除的这个 key 是该线程创建的,因而锁创建时必须携带执行线程的唯一特征以标示创建者的身份,在这里就是这个唯一 key 对应的 value。</p>
<p>如果加锁的线程出现异常 crash 了而不能及时删除锁,则会导致锁一直无法被正确释放,资源处于一直被占有,别的线程处于一直等待的状态。为了避免这样的情况,锁一定要在异常发生之后可以自己释放,以让出资源的控制权,可以使用 redis 的超时机制来达到这个目的。超时时间视不同的业务场景而定,一般是最大允许等待时间。需要注意的是,只有在加锁成功之后才可以对 key 设置 TTL,否则很容易导致 key 被多个线程不断设置 TTL 而无法过期。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> CONN.setnx(lockname, identifier):</span><br><span class="line"> CONN.expire(lockname, timeout)</span><br></pre></td></tr></table></figure>
<h3 id="加锁之后如何有效监测锁是否被篡改?"><a href="#加锁之后如何有效监测锁是否被篡改?" class="headerlink" title="加锁之后如何有效监测锁是否被篡改?"></a>加锁之后如何有效监测锁是否被篡改?</h3><p>redis 提供了 pipeline 和事务操作来保证多个命令可以在一个事务内全部完成从而减少多次网络请求带来的开销,watch 命令又可以在事务开始执行之前对所要操作的 key 执行监测,从而保证了事务的完整性和一致性。因此,为了防止锁篡改,可以在加锁完成之后对锁进行 watch 操作,一旦锁发生变化,则终止事务,回滚操作。</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">pipe = CONN.pipeline(<span class="keyword">True</span>)</span><br><span class="line">pipe.watch(lock)</span><br></pre></td></tr></table></figure>
<h3 id="提供锁的宿主机-redis-服务器-crash-导致锁不能被正确建立和释放该如何处理?"><a href="#提供锁的宿主机-redis-服务器-crash-导致锁不能被正确建立和释放该如何处理?" class="headerlink" title="提供锁的宿主机( redis 服务器) crash 导致锁不能被正确建立和释放该如何处理?"></a>提供锁的宿主机( redis 服务器) crash 导致锁不能被正确建立和释放该如何处理?</h3><p>不论是通信故障或是服务器故障而导致的锁服务器无法响应,此时都会导致客户端加锁和释放锁的请求无法完成,因此一定要有相应的应急处理,以确保程序流程的完整体验,加强客户端的健壮性。比如相应的超时提示,异常告警等。</p>
<h2 id="哪些边界需要注意"><a href="#哪些边界需要注意" class="headerlink" title="哪些边界需要注意"></a>哪些边界需要注意</h2><p>1.只有锁正确释放才算是整个事务的完整结束,如果锁释放失败,比如被篡改、锁服务器异常等,不同的业务可以根据自己的需求进行变动和调整。</p>
<p>2.设置 TTL 一定要在加锁成功之后,否则所有获取锁的客户端都会尝试 TTL 导致锁无法过期。</p>
<p>3.锁的过期时间也就是获取锁的客户端的最大等待时间,这个时间以执行的事务能够容忍的最长时间为限</p>
<h2 id="一个简单的-python-实现"><a href="#一个简单的-python-实现" class="headerlink" title="一个简单的 python 实现"></a>一个简单的 python 实现</h2><script src="//gist.github.com/5e474f42c8167ab32a6627ddd837fc83.js?file=redis_lock.py"></script>
<p>在上面这个实现中,如果锁释放(release_lock)失败,客户端可以尝试对当前的操作进行回滚或自定义处理方式。</p>
<p>如果业务的事务是在 redis 中执行的,完全可以用 redis 的 pipeline 和事务(开始前对锁键进行 watch )来完成所有操作,执行完成之后正确删除锁即可,这样 release_lock 的操作就可以不需要单独进行了。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>:</span><br><span class="line"> pipe.watch(lock)</span><br><span class="line"> pipe.multi()</span><br><span class="line"> <span class="comment">#TODO transaction with redis command</span></span><br><span class="line"> pipe.delete(lock)</span><br><span class="line"> pipe.unwatch(lock)</span><br><span class="line"><span class="keyword">except</span>:</span><br><span class="line"> <span class="comment">#TODO transaction failes</span></span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line"> <span class="comment">#TODO transaction success</span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure>
<p><a href="http://sanyuesha.com/about/">三月沙</a> <a href="http://sanyuesha.com/2016/08/20/distributed-lock-with-redis/">原文链接</a></p>
<h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p><strong>锁</strong></p>
<blockquote>
<p><a href="https://en.wikipedia.org/wiki/Lock_(computer_science">wiki</a>:In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechanism for enforcing limits on access to a resource in an environment where there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy.<br>在计算机科学中,锁或互斥量是一种同步机制,用于在多线程执行环境中,强行限制对资源访问。锁常被用于同步并发控制。</p>
</blockquote>
<p>简单来讲,锁是用来控制多线程执行对资源的并发访问的。比如当一个资源只允许在任意时刻只有一个执行线程对其进行写操作,那当其他线程要访问资源时,就必须要检查该该资源上是否存在<code>写操作锁</code>,如果存在,必须要等待锁的释放并获得锁之后才能对资源进行访问。</p>
<p><strong>悲观锁</strong></p>
<p>悲观锁假设在一个完整事务发生的过程中,总是会有其他线程会更改所操作的资源,因此线程总是对资源加锁之后才会对其做更改。</p>
<p><strong>乐观锁</strong></p>
<p>乐观锁假设在一个完整事务发生的过程中,不一定会有其他线程会更改资源,一旦发现资源被更改,则停止当前事务回滚所有操作。</p>
<p><strong>分布式锁</strong></p>
<p>常见的锁有多线程锁、数据库事务中的行级锁、表级锁等,这些锁的特点都是发生在单一系统环境中,如果需要在不同的进程、不同机器和系统等分布式环境中控制对资源的访问,这时候我们需要一把分布式锁。</p>
<p>不言而喻,分布式锁就是为了解决分布式环境中对资源的访问限制而诞生的。</p>
<h2 id="如何设计一把分布式锁"><a href="#如何设计一把分布式锁" class="headerlink" title="如何设计一把分布式锁"></a>如何设计一把分布式锁</h2>
我为什么创造了 turbo 这个后端的轮子
http://wecatch.me/2016/07/23/why-did-i-make-turbo/
2016-07-23T11:05:01.000Z
2016-07-25T15:57:11.000Z
<p><a href="http://sanyuesha.com/about/" target="_blank" rel="external">三月沙</a> <a href="http://sanyuesha.com/2016/07/23/why-did-i-make-turbo/" target="_blank" rel="external">原文链接</a></p>
<blockquote>
<p><a href="https://github.com/wecatch/app-turbo" target="_blank" rel="external">turbo</a> 与其说是一个 framework ,不如说是一个后端的解决方案。</p>
</blockquote>
<h2 id="turbo-是如何诞生的"><a href="#turbo-是如何诞生的" class="headerlink" title="turbo 是如何诞生的?"></a>turbo 是如何诞生的?</h2><p>tornado 是异步非阻塞的 web 服务器,同时也是一个 web framework,功能简单但完全够用。</p>
<p>原东家的技术栈是:tornado、mongodb、redis、openresty,最大的服务每天服务的独立用户有上百万。早期大部分项目完全使用 tornado 最原始的方式构建,一个 main 文件包含几百个路由,所有的 handler 分布在十几个大大小小的文件夹下面,项目基本的文件结构包含:</p>
<ul>
<li>一个抽象的 dal 层(数据抽象)</li>
<li>数据库连接(db)</li>
<li>配置(conf)</li>
<li>工具(utils)</li>
<li>缓存(cache)</li>
<li>第三方库(lib)</li>
</ul>
<p>随着项目的代码越来越多,多个特性开发常常同时进行,开发人员经常要非常小心地应对代码的更改,即使如此,冲突依然时常发生,代码的可维护性和健壮性日益变差。</p>
<p>创业公司由于业务面临飞速增长,多条业务线齐头并进,开发团队往往要同时维护很多个项目,有的项目生命周期很短,一次活动就结束了,有的项目要对外合作,随合作的结束而结束,有的项目服务新的产品,需要快速构建。不同的项目由不同的开发人员创建,创建项目的方式基本是 copy 旧项目,比较原始,容易出错。项目结构在不同的项目之间的差异随着业务的不同,差别渐渐凸显,重复的功能性代码和工具性代码也逐渐变多。</p>
<a id="more"></a>
<p>在这样的背景之下,影响后端开发效率和开发质量主要问题可归纳为:</p>
<ul>
<li>大项目路由多,不易管理,不易变更</li>
<li>项目目录分层不够,协同开发困难</li>
<li>构建没有标准,混乱不堪,可维护性差</li>
<li>重复性代码可共享性差,不易插拔</li>
<li>tornado 天生原始,没有 Session、数据库连接、配套的 mongodb ORM、缓存管理、RESTFul等丰富特性辅以提高开发效率。</li>
</ul>
<p>turbo 就是为解决这些问题而创建的,因而 turbo 更像是一个解决方案。</p>
<h2 id="turbo-具有什么特性?"><a href="#turbo-具有什么特性?" class="headerlink" title="turbo 具有什么特性?"></a>turbo 具有什么特性?</h2><ul>
<li>项目构建工具,一键生成项目目录和 app 结构</li>
<li>Session,提供灵活的接口,可以自定义存储方式</li>
<li>简单的 mongodb ORM,和 mongoose 相比,非常简单甚至粗糙,但足够用了</li>
<li>丰富且高度统一的目录划分,项目再大也不怕</li>
<li>代码可插拔,易移植,易维护</li>
<li>可以方便实现 RESTFul 风格的 api</li>
<li>灵活的路由组织结构,非常方便重构和查找</li>
</ul>
<h4 id="一键构建"><a href="#一键构建" class="headerlink" title="一键构建"></a>一键构建</h4><p>目录结构一键生成,不再是从旧项目拷贝,结构一致,高度统一,可维护性变强。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><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></pre></td><td class="code"><pre><span class="line">% turbo-admin -h </span><br><span class="line">turbo init project, server or app.</span><br><span class="line"> Usage:</span><br><span class="line"> turbo-admin (-h | --help)</span><br><span class="line"> turbo-admin startproject <project></span><br><span class="line"> turbo-admin startserver <server></span><br><span class="line"> turbo-admin startapp <app></span><br><span class="line"> turbo-admin index <model_name></span><br><span class="line"></span><br><span class="line"> Options:</span><br><span class="line"> -h, --help Show <span class="built_in">help</span> document</span><br><span class="line"></span><br><span class="line">% turbo-admin startproject hello-turbo</span><br><span class="line">% <span class="built_in">cd</span> hello-turbo</span><br><span class="line">% tree -L 2</span><br><span class="line">├── app-server</span><br><span class="line">│   ├── apps</span><br><span class="line">│   ├── main.py</span><br><span class="line">│   ├── setting.py</span><br><span class="line">│   ├── static</span><br><span class="line">│   └── templates</span><br><span class="line">├── config</span><br><span class="line">│   └── __init__.py</span><br><span class="line">├── db</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── conn.py</span><br><span class="line">│   └── setting.py</span><br><span class="line">├── helpers</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── settings.py</span><br><span class="line">│   └── user</span><br><span class="line">├── lib</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   └── session.py</span><br><span class="line">├── models</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── base.py</span><br><span class="line">│   ├── settings.py</span><br><span class="line">│   └── user</span><br><span class="line">├── service</span><br><span class="line">│   └── __init__.py</span><br><span class="line">├── store</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── actions.py</span><br><span class="line">│   ├── modules</span><br><span class="line">│   └── mutation_types.py</span><br><span class="line">└── utils</span><br><span class="line"> └── __init__.py</span><br></pre></td></tr></table></figure>
<h4 id="Session"><a href="#Session" class="headerlink" title="Session"></a>Session</h4><p>自带 Session,存储方式可以自由变更,灵活性强。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">from</span> turbo.session <span class="keyword">import</span> HeaderObject, CookieObject</span><br><span class="line"><span class="keyword">from</span> turbo.session <span class="keyword">import</span> Store, RedisStore, DiskStore</span><br></pre></td></tr></table></figure>
<h4 id="简单的-mongodb-ORM"><a href="#简单的-mongodb-ORM" class="headerlink" title="简单的 mongodb ORM"></a>简单的 mongodb ORM</h4><p>ORM 提供最基本的 collection 结构到代码的映射,表结构清晰明了,增删改均可灵活定义数据一致性检查。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> turbo.model</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Category</span><span class="params">(turbo.model.BaseModel)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="string">"""分类</span><br><span class="line"></span><br><span class="line"> field:</span><br><span class="line"> name: 名称</span><br><span class="line"> ename: 英文名</span><br><span class="line"> desc: 描述</span><br><span class="line"> tag: 标签列表</span><br><span class="line"> sn: 序号</span><br><span class="line"> nvideo: 视频数量</span><br><span class="line"> cover: 封面</span><br><span class="line"> uid: 创建用户</span><br><span class="line"> atime: 时间</span><br><span class="line"> """</span></span><br><span class="line"></span><br><span class="line"> name = <span class="string">'category'</span></span><br><span class="line"> field = {</span><br><span class="line"> <span class="string">'name'</span>: (basestring, <span class="string">''</span>),</span><br><span class="line"> <span class="string">'ename'</span>: (basestring, <span class="string">''</span>),</span><br><span class="line"> <span class="string">'desc'</span>: (basestring, <span class="string">''</span>),</span><br><span class="line"> <span class="string">'tag'</span>: (list, []),</span><br><span class="line"></span><br><span class="line"> <span class="string">'sn'</span>: (int, <span class="number">0</span>),</span><br><span class="line"> <span class="string">'nvideo'</span>: (int, <span class="number">0</span>),</span><br><span class="line"></span><br><span class="line"> <span class="string">'cover'</span>: (ObjectId, <span class="keyword">None</span>),</span><br><span class="line"> <span class="string">'uid'</span>: (ObjectId, <span class="keyword">None</span>),</span><br><span class="line"></span><br><span class="line"> <span class="string">'atime'</span>: (datetime, <span class="keyword">None</span>),</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h4 id="丰富的目录结构划分"><a href="#丰富的目录结构划分" class="headerlink" title="丰富的目录结构划分"></a>丰富的目录结构划分</h4><p>严格定义了各个目录的结构的作用以及层次,提供了一般大型项目所需的各种目录划分</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">├── README.md</span><br><span class="line">├── app-server <span class="comment"># 业务 server</span></span><br><span class="line">├── config <span class="comment"># 全局配置</span></span><br><span class="line">├── db <span class="comment"># db 连接</span></span><br><span class="line">├── helpers <span class="comment"># 全局业务逻辑</span></span><br><span class="line">├── lib <span class="comment"># 第三方依赖库</span></span><br><span class="line">├── models <span class="comment"># 表结构</span></span><br><span class="line">├── script <span class="comment"># 一次性脚本</span></span><br><span class="line">├── service <span class="comment"># 业务性工具</span></span><br><span class="line">├── store <span class="comment"># 全局状态管理</span></span><br><span class="line">├── task <span class="comment"># 异步任务</span></span><br><span class="line">├── utils <span class="comment"># 非业务性工具</span></span><br></pre></td></tr></table></figure>
<h4 id="代码可插拔-易移植"><a href="#代码可插拔-易移植" class="headerlink" title="代码可插拔, 易移植"></a>代码可插拔, 易移植</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">apps</span><br><span class="line">├── __init__.py</span><br><span class="line">├── app</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── app.py</span><br><span class="line">│   ├── base.py</span><br><span class="line">│   ├── setting.py</span><br><span class="line">├── base.py</span><br><span class="line">├── settings.py</span><br></pre></td></tr></table></figure>
<p>每个业务 app 都具有一样的结构,配置和业务代码随着业务所需分布在各个不同的层次,抽象程度越高的代码,所在层次越高,比如全局合法性检查可以放置在 <code>apps/base.py</code> 中,越具体的业务所在的层次越低,比如具体的注册登录逻辑可以放置在 <code>apps/app/app.py</code> 中。如果业务发生变更,业务目录 <code>app</code> 可完整移植。</p>
<p>所有业务 <code>app</code> 都必须在 <code>apps/settings.py</code> 下注册才能生效,上下线 so easy.</p>
<h4 id="RESTFul-api"><a href="#RESTFul-api" class="headerlink" title="RESTFul api"></a>RESTFul api</h4><p>http 协议所支持的方法都可以在 turbo 中以大小写的方式区分来实现普通页面 render 和接口 api</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># render </span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self)</span>:</span></span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"> </span><br><span class="line"><span class="comment"># api </span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">GET</span><span class="params">(self)</span>:</span></span><br><span class="line"> self._data = {<span class="string">'username'</span>: <span class="string">'zhyq0826'</span>}</span><br></pre></td></tr></table></figure>
<h4 id="灵活的路由"><a href="#灵活的路由" class="headerlink" title="灵活的路由"></a>灵活的路由</h4><p>路由紧随业务代码,turbo 提供了</p>
<ul>
<li>register_group_urls</li>
<li>register_url</li>
</ul>
<p>两种方式来实现路由的注册</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">register.register_group_urls(<span class="string">''</span>, [</span><br><span class="line"> (<span class="string">'/'</span>, app.HomeHandler),</span><br><span class="line"> (<span class="string">'/index'</span>, app.HomeHandler, <span class="string">'index'</span>),</span><br><span class="line"> (<span class="string">'/hello'</span>, app.HomeHandler, <span class="string">'home'</span>),</span><br><span class="line">])</span><br><span class="line"></span><br><span class="line">register.register_url(<span class="string">'/v1/hello'</span>, app.ApiHandler)</span><br></pre></td></tr></table></figure>
<p>可以十分方便的更改路由规则。</p>
<h4 id="集成很多实用的功能特性"><a href="#集成很多实用的功能特性" class="headerlink" title="集成很多实用的功能特性"></a>集成很多实用的功能特性</h4><p>在使用mongodb 和 tornado 的实际开发中遇到了各式各样的问题,因而 turbo 自身在发展过程中集成了很多小功能,非常实用。</p>
<p>1.可以使用命令行一键生成所有 collection 的索引</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">turbo-admin index <model_name></span><br></pre></td></tr></table></figure>
<p>2.类似 flux 的状态和事件分发机制,可以非常方便管理和更改全局变量状态,此特性可以用以请求统计,函数调用统计等方面</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#user.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> turbo.flux <span class="keyword">import</span> Mutation, register, State</span><br><span class="line"></span><br><span class="line">mutation = Mutation(__file__)</span><br><span class="line">state = State(__file__)</span><br><span class="line"></span><br><span class="line"><span class="meta">@register(mutation)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">increase_rank</span><span class="params">(rank)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> rank+<span class="number">1</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@register(mutation)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">dec_rank</span><span class="params">(rank)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> rank<span class="number">-1</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">#actions.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> turbo.flux <span class="keyword">import</span> Mutation, register, dispatch, register_dispatch</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> mutation_types</span><br><span class="line"></span><br><span class="line"><span class="meta">@register_dispatch('user', mutation_types.INCREASE)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">increase</span><span class="params">(rank)</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"><span class="function"><span class="keyword">def</span> <span class="title">decrease</span><span class="params">(rank)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> dispatch(<span class="string">'user'</span>, mutation_types.DECREASE, rank)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@register_dispatch('metric', 'inc_qps')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">inc_qps</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure>
<p>3.mongodb 读写调用 hook,利用该 hook ,可以统计 mongodb 读写调用状况</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Model</span><span class="params">(turbo.model.BaseModel)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">write_action_call</span><span class="params">(self, name, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="string">"""</span><br><span class="line"> execute when write action occurs, note: in this method write action must be called asynchronously</span><br><span class="line"> """</span></span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">read_action_call</span><span class="params">(self, name, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="string">"""</span><br><span class="line"> execute when read action occurs, note: in this method read action must be called asynchronously</span><br><span class="line"> """</span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure>
<p>4.日志可以根据文件所在路径生成 logger 名称,也可以自定义 logger 路径、级别、名称等<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><br><span class="line"><span class="keyword">from</span> turbo.util <span class="keyword">import</span> getLogger</span><br><span class="line"></span><br><span class="line">logger = getLogger(__file__)</span><br><span class="line"></span><br><span class="line">logger = getLogger(<span class="string">'feed'</span>, log_level=logging.DEBUG, log_path=<span class="string">'/var/log/feed.log'</span>)</span><br></pre></td></tr></table></figure></p>
<p>5.字符串化 mongodb 数据</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">from</span> turbo.util <span class="keyword">import</span> to_str, to_list_str, to_dict_str, json_encode</span><br><span class="line"></span><br><span class="line">data = json_encode(to_str(db.collection.find()))</span><br></pre></td></tr></table></figure>
<p>6.参数快捷提取和转换</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">_get_params = {</span><br><span class="line"> <span class="string">'need'</span>: [</span><br><span class="line"> (<span class="string">'skip'</span>, int),</span><br><span class="line"> (<span class="string">'limit'</span>, int),</span><br><span class="line"> ],</span><br><span class="line"> <span class="string">'option'</span>: [</span><br><span class="line"> (<span class="string">'did'</span>, basestring, <span class="keyword">None</span>)</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可调用 self.parameters 完成参数提取工作,必须的参数不存在或转换错误时,值为 <code>None</code>,可选的参数可以指定默认值。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">assert</span> self.parameters[<span class="string">'skip'</span>] == <span class="number">0</span></span><br><span class="line"><span class="keyword">assert</span> self.parameters[<span class="string">'did'</span>] == <span class="keyword">None</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>除上面列举的之外,turbo 还具有很多实用的特性,仓库地址:<a href="https://github.com/wecatch/app-turbo" target="_blank" rel="external">https://github.com/wecatch/app-turbo</a></p>
</blockquote>
<h2 id="适时而建的轮子-turbo"><a href="#适时而建的轮子-turbo" class="headerlink" title="适时而建的轮子 turbo"></a>适时而建的轮子 turbo</h2><p>turbo 在生产环境中应用了近一年,最大的项目日独立用户过百万,还有十多个大大小小的项目的后端使用了 turbo,已经经过了最初的考验。</p>
<p>后端的轮子相比前端来说,远不如后者的来的快、来的猛,后端需要更长久的磨练和雕琢,一点一滴都需要经过业务的锤炼和数据的洗礼,才能渐成佳品,成为解决特定问题的利器。而轮子的创造更要把握时机,顺势而发,过早,轮子不够健全,解决问题不够彻底,很难推广使用,甚至有可能成了绊脚石,过晚,很多问题已经导致代码积劳成疾,想要完全治愈,必然代价不菲。</p>
<p>turbo 正是为了解决 tornado 和 mongodb 类应用在面对代码规模庞大、多人协同所遭遇的种种问题而创造的,它的立足点非常实际,没有炫技,不浮夸,切中要害,直面问题。</p>
<p>turbo 的实用就是它的华丽所在。</p>
<ul>
<li><a href="https://github.com/wecatch/app-turbo" target="_blank" rel="external">Github</a> </li>
<li>QQ群交流: 387199856</li>
</ul>
<p><a href="http://sanyuesha.com/about/">三月沙</a> <a href="http://sanyuesha.com/2016/07/23/why-did-i-make-turbo/">原文链接</a></p>
<blockquote>
<p><a href="https://github.com/wecatch/app-turbo">turbo</a> 与其说是一个 framework ,不如说是一个后端的解决方案。</p>
</blockquote>
<h2 id="turbo-是如何诞生的"><a href="#turbo-是如何诞生的" class="headerlink" title="turbo 是如何诞生的?"></a>turbo 是如何诞生的?</h2><p>tornado 是异步非阻塞的 web 服务器,同时也是一个 web framework,功能简单但完全够用。</p>
<p>原东家的技术栈是:tornado、mongodb、redis、openresty,最大的服务每天服务的独立用户有上百万。早期大部分项目完全使用 tornado 最原始的方式构建,一个 main 文件包含几百个路由,所有的 handler 分布在十几个大大小小的文件夹下面,项目基本的文件结构包含:</p>
<ul>
<li>一个抽象的 dal 层(数据抽象)</li>
<li>数据库连接(db)</li>
<li>配置(conf)</li>
<li>工具(utils)</li>
<li>缓存(cache)</li>
<li>第三方库(lib)</li>
</ul>
<p>随着项目的代码越来越多,多个特性开发常常同时进行,开发人员经常要非常小心地应对代码的更改,即使如此,冲突依然时常发生,代码的可维护性和健壮性日益变差。</p>
<p>创业公司由于业务面临飞速增长,多条业务线齐头并进,开发团队往往要同时维护很多个项目,有的项目生命周期很短,一次活动就结束了,有的项目要对外合作,随合作的结束而结束,有的项目服务新的产品,需要快速构建。不同的项目由不同的开发人员创建,创建项目的方式基本是 copy 旧项目,比较原始,容易出错。项目结构在不同的项目之间的差异随着业务的不同,差别渐渐凸显,重复的功能性代码和工具性代码也逐渐变多。</p>
How about a nice cup of React
http://wecatch.me/2016/07/05/how-about-a-nice-cup-of-React/
2016-07-04T17:25:41.000Z
2016-07-23T11:24:20.000Z
<p><a href="http://braavos.me" target="_blank" rel="external">落在深海</a> <a href="http://braavos.me/blog/2016/07/05/how-about-a-nice-cup-of-react/" target="_blank" rel="external">原文链接</a></p>
<p>接触 React 一年左右,期间在单页面、单页应用都有使用, 它极大地改变了我个人的前端开发方式。为方便快速出产品,闲暇之余也尝试写了点基础组件: <a href="http://github.com/jerryshew/react-component" target="_blank" rel="external">react-component</a>, <a href="http://github.com/jerryshew/react-image-cropper" target="_blank" rel="external">react-image-cropper</a>,<a href="http://github.com/jerryshew/react-touch-gallery" target="_blank" rel="external">react-touch-gallery</a>, <a href="http://github.com/jerryshew/react-file-upload" target="_blank" rel="external">react-file-upload</a>。也一直计划着写点心得总结什么的,迫于忙、浮躁,难静下心写东西,屡次作罢。这两天刚离职,决定把这篇总结补上。</p>
<a id="more"></a>
<p>在 React 之前,玩过一点点 Angular 跟 Backbone,Backbone 是比较轻量级 MVC,Angular 较重,它的指令、注入式写法, 较死板的规则,不够简洁的 API,诸多概念等等,最终只是被我当玩具折腾了下。由于接触到项目多数时间花在 view 的交互、事件、变化更新上,所以大多数情况还是选择熟悉的 jQuery,原因无非是:熟练,可控,最轻依赖,快速灵活。过程基本上是: 命名 DOM,给 DOM 加特效,取数据更新 DOM, 绑定事件到 DOM 等等。时间一久,问题就很明显了:即使多次尝试做不同层次的抽象分层,还是很乱,太频繁与 DOM 打交道,遇到问题不好排查,维护也很痛苦。如果能尽量少碰 DOM,以数据、逻辑导向来写 view 呢?</p>
<p>Github 上有 xx 步从 jQuery 到 Backbone, 就是通过一步步迭代,解释 Backbone 如何抽象分层,将数据、逻辑、事件、view 操作做分离,使得层级清晰,逻辑分明。可如果只为解决 view 层问题的话,是没必要引入过多概念和规则的,且 view 本身的问题,例如高复用性等也没有被解决。</p>
<p>React 带着变革前端开发的使命而来,声称专注 view 层,特点是组件式开发,有着简洁合理的 API,不错性能等优势。</p>
<p>说到组件式,大概包括了外部属性跟内部状态。比如电视盒子,对屏幕来说,只需要提供几种插口;内部构造对外是透明的,根据插口接入的不同,呈现截然不同的输出,屏幕也无需知道盒子内部的运作方式。实际上用 jQuery 时,大家也多多少少封装过一些『组件』,供外部使用,比如购物车 Counter,自定义播放器等,但并不会像 React 那样通通都是组件。在 React 里,组件包含 props(外部属性)跟 state(内部状态), 具体细节可以去看 React 官网。</p>
<p>初次对组件式思维赞叹的是评论的加载更多功能。一般用 jQuery 的话,思路是请求数据,组装 list DOM ,append 上去; 换做组件式思路呢?答案是数据驱动。评论的数据应该属于评论内部状态,那么加载更多调整内部状态就好了(不断练习这种数据导向的思维,合理设计 props 跟 state,对于通常项目需求,写起来就非常快了,同时也基本避开了对 DOM 的操作),React 帮你把状态的更新高效的反映到 DOM 更新。</p>
<p>逐渐对组件式开发轻车熟路,反过来也不断巩固组件化开发思维,于是开始真正信服 React 的 Learn once, write anywhere.</p>
<p>组件真的那么好么?</p>
<p>组件堆叠组合的方式让高复用变得简单,然而在开发中还是遇到了些问题:1. 刚上手很快就遇到组件如何通信的问题,看了 React 文档才弄明白;2. 在写 Dropdown 时,改变外部默认选中值并不会导致 Dropdown 选中值变化。当时头脑不清晰,折腾了半天,这个问题归纳为:<strong>state 依赖于 props 的初始值(该变化后也需向内传递), 而 state 也通过自身内部维护</strong>,这个问题有很多使用场景,这里内部维护是指 Dropdown 选中来改变 state。那么如何让 props 改变再来触发 state 呢,看了 React 生命周期才知道有 <code>componentWillReceiveProps</code> 这样的方法,也不得不赞叹 React 生命周期设计的周全,这个问题在后来用 Ember.js 写组件时同样也遇到了,只是实现稍有不同;3. 组件层级深,层层传递 props 到子组件很麻烦,React 提供了 context,使子组件方便的获取父组件链上下文 props;4. 组件的被动方法如何调用,例如点击按钮触发组件的操作?通过 refs 可以拿到组件实例,从而调用实例方法,而 <code>findDOMNode</code> 则能拿到 DOM 元素等等。不断有新问题涌出,问题也在一一被解决,React 之路进阶还在继续。</p>
<p>当然组件模式并不是只有 React 一家, Ember 2.0 弱化了 controller, 加入 component 的概念, Vue.js 也是组件的思路,甚至更轻量,简洁优雅。为什么选择 React ? 原因有几点:相比 Emberjs 这样的 MVC,React 轻量、概念少,简单清晰,高效的 DOM diff 算法保证了性能,只做 view 且做的足够好。Vue 是后起之秀,高性能,更简洁,十足野心要替代 React, 在前公司生产环境投入使用,遇到几个问题:1. 在 template 里用 computed 属性、method 甚至是 data 看起来傻傻分不清楚,component 里 data 跟 props 也差不多,协作的话需有团队约束。2. 看似比 JSX 片段清晰的 template,实际上却因语法导致引号、括号、字符串、方法调用等使用造成视觉混乱。3. 还是模板问题,指令必须依附 DOM,逻辑好不清晰(据说这点在2.0得到改善)。基本上都是关于 template,也不算太大问题。</p>
<p>有人说经过很多年发展,终于模板跟 js 逻辑分离了,React 一下子又倒退回去了。说出这言论的一定没用 React,当你真正体验 React 的『合并』后或许才能体会到它的方便之处。如果你也曾经历模板逻辑分离的不同时期,回头想想,分离真的那么好么?逻辑部分还好,而模板呢功能太弱,要做到精确控制输出很费力,而且脱离逻辑谈复用性也并不靠谱,真的是我们想要的么?React 的 JSX 用类 XML 的语法糖,使得可以通过 JS 语法来方便精确控制输出逻辑,再加上组件作用域的光环也显得十分合理。</p>
<p>总结下吧,React 带来了前所未有的开发体验,也引领了组件式开发的热潮,对于开发者来说,优势很多,例如: </p>
<ol>
<li>learn once, write everywhere.</li>
<li>模板跟逻辑在一起,逻辑精确方便的控制输出。</li>
<li>组件式开发思维,让开发者专注数据、逻辑。</li>
<li>简洁清晰的 API,较少的概念,易上手。</li>
<li>周边生态逐渐完善,开发者也很容易做出贡献。</li>
<li>社区活跃,库本身在 Facebook 及很多大厂使用,能力得到充分验证。<br>…</li>
</ol>
<p>问题、疑惑及待解决: </p>
<ol>
<li>方法普遍太长(各种编辑器补全插件可以有效解决)</li>
<li>生命周期的不同阶段需要弄清楚,不然会遇到些奇怪的问题。</li>
<li>组件化必然导致状态的零散,如何集中管理状态,又如何通知更新所有组件状态,可以去研究下 flux 跟 redux,但各有缺点。</li>
<li>模板跟逻辑在一起,更容易写不好,乱,对人要求不算低。</li>
<li>组件样式编写问题。</li>
</ol>
<p>注意: 本文主观色彩较严重,细节并不考究,若有问题欢迎指出。</p>
<p>好吧,更多的,以后想到了再扯吧。</p>
<p><a href="http://braavos.me">落在深海</a> <a href="http://braavos.me/blog/2016/07/05/how-about-a-nice-cup-of-react/">原文链接</a></p>
<p>接触 React 一年左右,期间在单页面、单页应用都有使用, 它极大地改变了我个人的前端开发方式。为方便快速出产品,闲暇之余也尝试写了点基础组件: <a href="http://github.com/jerryshew/react-component">react-component</a>, <a href="http://github.com/jerryshew/react-image-cropper">react-image-cropper</a>,<a href="http://github.com/jerryshew/react-touch-gallery">react-touch-gallery</a>, <a href="http://github.com/jerryshew/react-file-upload">react-file-upload</a>。也一直计划着写点心得总结什么的,迫于忙、浮躁,难静下心写东西,屡次作罢。这两天刚离职,决定把这篇总结补上。</p>
你真得理解 python 的浅拷贝和深拷贝吗?
http://wecatch.me/2016/06/18/python-copy-deepcopy/
2016-06-18T09:00:28.000Z
2016-06-18T09:09:40.000Z
<p><a href="http://sanyuesha.com/about/" target="_blank" rel="external">三月沙</a> <a href="http://sanyuesha.com/2016/06/18/python-copy-deepcopy/" target="_blank" rel="external">原文链接</a></p>
<p>为了让一个对象发生改变时不对原对象产生副作用,此时,需要一份这个对象的拷贝,python 提供了 copy 机制来完成这样的任务,对应的模块是 <a href="https://docs.python.org/2/library/copy.html" target="_blank" rel="external">copy</a>。</p>
<h2 id="浅拷贝-shadow-copy"><a href="#浅拷贝-shadow-copy" class="headerlink" title="浅拷贝:shadow copy"></a>浅拷贝:shadow copy</h2><p>在 <code>copy</code> 模块中,有 <code>copy</code> 函数可以完成浅拷贝。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> copy <span class="keyword">import</span> copy</span><br></pre></td></tr></table></figure></p>
<p>在 python 中,标识一个对象唯一身份的是:对象的<code>id</code>(内存地址),对象类型,对象值,而浅拷贝就是创建一个具有相同类型,相同值但不同<code>id</code>的新对象。</p>
<p>对可变对象而言,对象的值一样可能包含有对其他对象的引用,浅拷贝产生的新对象,虽然具有完全不同的<code>id</code>,但是其值若包含可变对象,这些对象和原始对象中的值包含同样的引用。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span><span class="keyword">import</span> copy</span><br><span class="line"><span class="meta">>>> </span>l = {<span class="string">'a'</span>: [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>], <span class="string">'b'</span>:[<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]}</span><br><span class="line"><span class="meta">>>> </span>c = copy.copy(l)</span><br><span class="line"><span class="meta">>>> </span>id(l) == id(c)</span><br><span class="line"><span class="keyword">False</span></span><br><span class="line"><span class="meta">>>> </span>l[<span class="string">'a'</span>].append(<span class="string">'4'</span>)</span><br><span class="line"><span class="meta">>>> </span>c[<span class="string">'b'</span>].append(<span class="string">'7'</span>)</span><br><span class="line"><span class="meta">>>> </span>l</span><br><span class="line">{<span class="string">'a'</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="string">'4'</span>], <span class="string">'b'</span>: [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="string">'7'</span>]}</span><br><span class="line"><span class="meta">>>> </span>c</span><br><span class="line">{<span class="string">'a'</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="string">'4'</span>], <span class="string">'b'</span>: [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="string">'7'</span>]}</span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<p>可见浅拷贝产生的新对象中可变对象的值在发生改变时会对原对象的值产生副作用,因为这些值是同一个引用。</p>
<a id="more"></a>
<p>浅拷贝仅仅对对象自身创建了一份拷贝,而没有在进一步处理对象中包含的值。因此使用浅拷贝的典型使用场景是:对象自身发生改变的同时需要保持对象中的值完全相同,比如 list 排序。</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></pre></td><td class="code"><pre><span class="line">>>> def sorted_list(olist, key=None):</span><br><span class="line">... copied_list = copy.copy(olist)</span><br><span class="line">... copied_list.sort(key=key)</span><br><span class="line">... return copied_list</span><br><span class="line">... </span><br><span class="line">>>> a = [3,2,1]</span><br><span class="line">>>> b = sorted_list(a)</span><br><span class="line">>>> a</span><br><span class="line">[3, 2, 1]</span><br><span class="line">>>> b</span><br><span class="line">[1, 2, 3]</span><br></pre></td></tr></table></figure>
<h2 id="深拷贝-deep-copy"><a href="#深拷贝-deep-copy" class="headerlink" title="深拷贝:deep copy"></a>深拷贝:deep copy</h2><p>在 <code>copy</code> 模块中,有 <code>deepcopy</code> 函数可以完成深拷贝。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> copy <span class="keyword">import</span> deepcopy</span><br></pre></td></tr></table></figure></p>
<p>深拷贝不仅仅拷贝了原始对象自身,也对其包含的值进行拷贝,它会递归的查找对象中包含的其他对象的引用,来完成更深层次拷贝。因此,深拷贝产生的副本可以随意修改而不需要担心会引起原始值的改变。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span><span class="keyword">import</span> copy</span><br><span class="line"><span class="meta">>>> </span>l = {<span class="string">'a'</span>: [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>], <span class="string">'b'</span>:[<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]}</span><br><span class="line"><span class="meta">>>> </span>c = copy.deepcopy(l)</span><br><span class="line"><span class="meta">>>> </span>id(l) == id(c)</span><br><span class="line"><span class="keyword">False</span></span><br><span class="line"><span class="meta">>>> </span>l[<span class="string">'a'</span>].append(<span class="string">'4'</span>)</span><br><span class="line"><span class="meta">>>> </span>c[<span class="string">'b'</span>].append(<span class="string">'7'</span>)</span><br><span class="line"><span class="meta">>>> </span>l</span><br><span class="line">{<span class="string">'a'</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="string">'4'</span>], <span class="string">'b'</span>: [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>]}</span><br><span class="line"><span class="meta">>>> </span>c</span><br><span class="line">{<span class="string">'a'</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], <span class="string">'b'</span>: [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="string">'7'</span>]}</span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<p>值得注意的是,深拷贝并非完完全全递归查找所有对象,因为一旦对象引用了自身,完全递归可能会导致无限循环。一个对象被拷贝了,python 会对该对象做个标记,如果还有其他需要拷贝的对象引用着该对象,<strong>它们的拷贝其实指向的是同一份拷贝</strong></p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>a = [<span class="number">1</span>,<span class="number">2</span>]</span><br><span class="line"><span class="meta">>>> </span>b = [a,a]</span><br><span class="line"><span class="meta">>>> </span>b</span><br><span class="line">[[<span class="number">1</span>, <span class="number">2</span>], [<span class="number">1</span>, <span class="number">2</span>]]</span><br><span class="line"><span class="meta">>>> </span>c = deepcopy(b)</span><br><span class="line"><span class="meta">>>> </span>id(b[<span class="number">0</span>]) == id(c[<span class="number">0</span>])</span><br><span class="line"><span class="keyword">False</span></span><br><span class="line"><span class="meta">>>> </span>id(b[<span class="number">0</span>]) == id(b[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">True</span></span><br><span class="line"><span class="meta">>>> </span>c</span><br><span class="line">[[<span class="number">1</span>, <span class="number">2</span>], [<span class="number">1</span>, <span class="number">2</span>]]</span><br><span class="line"><span class="meta">>>> </span>c[<span class="number">0</span>].append(<span class="number">3</span>) <span class="comment">#c list 中包含的两份拷贝指向同一处</span></span><br><span class="line"><span class="meta">>>> </span>c</span><br><span class="line">[[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]]</span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<h2 id="自定义拷贝机制"><a href="#自定义拷贝机制" class="headerlink" title="自定义拷贝机制"></a>自定义拷贝机制</h2><p>使用 <code>_copy_</code> 和 <code>__deepcopy__</code> 可以完成对一个对象拷贝的定制。这里不展开了,有机会再探讨自定义拷贝。</p>
<p><a href="http://sanyuesha.com/about/">三月沙</a> <a href="http://sanyuesha.com/2016/06/18/python-copy-deepcopy/">原文链接</a></p>
<p>为了让一个对象发生改变时不对原对象产生副作用,此时,需要一份这个对象的拷贝,python 提供了 copy 机制来完成这样的任务,对应的模块是 <a href="https://docs.python.org/2/library/copy.html">copy</a>。</p>
<h2 id="浅拷贝-shadow-copy"><a href="#浅拷贝-shadow-copy" class="headerlink" title="浅拷贝:shadow copy"></a>浅拷贝:shadow copy</h2><p>在 <code>copy</code> 模块中,有 <code>copy</code> 函数可以完成浅拷贝。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> copy <span class="keyword">import</span> copy</span><br></pre></td></tr></table></figure></p>
<p>在 python 中,标识一个对象唯一身份的是:对象的<code>id</code>(内存地址),对象类型,对象值,而浅拷贝就是创建一个具有相同类型,相同值但不同<code>id</code>的新对象。</p>
<p>对可变对象而言,对象的值一样可能包含有对其他对象的引用,浅拷贝产生的新对象,虽然具有完全不同的<code>id</code>,但是其值若包含可变对象,这些对象和原始对象中的值包含同样的引用。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span><span class="keyword">import</span> copy</span><br><span class="line"><span class="meta">>>> </span>l = {<span class="string">'a'</span>: [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>], <span class="string">'b'</span>:[<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]}</span><br><span class="line"><span class="meta">>>> </span>c = copy.copy(l)</span><br><span class="line"><span class="meta">>>> </span>id(l) == id(c)</span><br><span class="line"><span class="keyword">False</span></span><br><span class="line"><span class="meta">>>> </span>l[<span class="string">'a'</span>].append(<span class="string">'4'</span>)</span><br><span class="line"><span class="meta">>>> </span>c[<span class="string">'b'</span>].append(<span class="string">'7'</span>)</span><br><span class="line"><span class="meta">>>> </span>l</span><br><span class="line">{<span class="string">'a'</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="string">'4'</span>], <span class="string">'b'</span>: [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="string">'7'</span>]}</span><br><span class="line"><span class="meta">>>> </span>c</span><br><span class="line">{<span class="string">'a'</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="string">'4'</span>], <span class="string">'b'</span>: [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="string">'7'</span>]}</span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<p>可见浅拷贝产生的新对象中可变对象的值在发生改变时会对原对象的值产生副作用,因为这些值是同一个引用。</p>
为什么选择 emberjs 开发 dashboard 和 CMS 类系统
http://wecatch.me/2016/06/04/why-emberjs-is-best-for-cms/
2016-06-04T10:35:35.000Z
2016-06-16T15:17:00.000Z
<p><a href="http://sanyuesha.com/about/" target="_blank" rel="external">三月沙</a> <a href="http://sanyuesha.com/2016/06/04/why-emberjs-is-best-for-cms/" target="_blank" rel="external">原文链接</a> </p>
<h2 id="emberjs-是什么"><a href="#emberjs-是什么" class="headerlink" title="emberjs 是什么"></a>emberjs 是什么</h2><p>在开始之前,先看一组 github 数据</p>
<p><strong>ember.js</strong></p>
<ul>
<li>star 16291</li>
<li>contributors 586</li>
<li>releases 181</li>
<li>issues 167 open, 4397 closed</li>
</ul>
<p><a href="https://github.com/emberjs/ember.js" target="_blank" rel="external">https://github.com/emberjs/ember.js</a></p>
<p><strong>angular.js</strong></p>
<ul>
<li>star 49643</li>
<li>contributors 1476</li>
<li>releases 169</li>
<li>issues 817 open, 6992 closed</li>
</ul>
<p><a href="https://github.com/angular/angular.js" target="_blank" rel="external">https://github.com/angular/angular.js</a></p>
<p><strong>vue.js</strong></p>
<ul>
<li>star 19883</li>
<li>contributors 69</li>
<li>releases 137</li>
<li>issues 27 open, 2252 closed</li>
</ul>
<p><a href="https://github.com/vuejs/vue" target="_blank" rel="external">https://github.com/vuejs/vue</a></p>
<p><strong>react.js</strong></p>
<ul>
<li>star 43026</li>
<li>contributors 713</li>
<li>releases 46</li>
<li>issues 480 open, 2843 closed</li>
</ul>
<p><a href="https://github.com/facebook/react" target="_blank" rel="external">https://github.com/facebook/react</a></p>
<p><strong>riot.js</strong></p>
<ul>
<li>star 9565</li>
<li>contributors 138</li>
<li>releases 137</li>
<li>issues 96 open, 1089 closed</li>
</ul>
<p><a href="https://github.com/riot/riot" target="_blank" rel="external">https://github.com/riot/riot</a></p>
<blockquote>
<p>注: 统计时间为本文编写之时</p>
</blockquote>
<p>emberjs 和其他同类框架一样,至少在 github 的 表现足以证明其流行度是不低的,但各个框架的诞生的场景以及解决的问题又不尽相同。</p>
<p>emberjs 是基于 jquery 的一个全栈式前端框架,它提供了从路由层-> view 层->数据和网络交互层的全部解决方案,只用 emberjs 就足以解决前端单页应用遇到的所有复杂问题,而且兼具有不俗的开发效率和开发质量。</p>
<h4 id="全栈式解决方案"><a href="#全栈式解决方案" class="headerlink" title="全栈式解决方案"></a>全栈式解决方案</h4><p>emberjs 是一个大而全的框架,它几乎囊括了单页应用开发所需要的方方面面: component,model,service,route,temlate<br>,ember-data。借助 emberjs 几乎可以完成任何规模的复杂应用,而不用担心它没有提供足够丰富的特性或者需要求助其他第三方库(不一定可以很好的和框架本身集成)。</p>
<a id="more"></a>
<h4 id="基于-jquery"><a href="#基于-jquery" class="headerlink" title="基于 jquery"></a>基于 jquery</h4><p>emberjs 是完全基于 jquery 的,意味着任何基于 jquery 的第三方库都可以和 ember app 集成,而这些年构建于 jquery 之上的框架和插件不计其数,在没有必要重复造轮子的地方,完全可以用丰富的第三方轮子替代,比如 bootstrap,semantic-ui 这种大而全的 UI framework,jQuery-File-Upload 这种可以兼容各种主流浏览器的文件上传组件。利用这一优势,大量现成的 jquery 插件能很快集在 ember app 中,借助 ember component 又可以很轻松达到复用的目的。</p>
<h4 id="良好的兼容性"><a href="#良好的兼容性" class="headerlink" title="良好的兼容性"></a>良好的兼容性</h4><p>最低能够兼容到 IE 8,新版本也在逐步淘汰低版本IE的兼容。</p>
<h4 id="约定优于配置"><a href="#约定优于配置" class="headerlink" title="约定优于配置"></a>约定优于配置</h4><p>开发、管理和维护复杂的单页应用,是一件非常考验组织架构的事情,组件的复用、网络的交互、全局状态的管理、复杂的表单数据、路由等都需要经过精心设计和严格的代码规范才能最大限度的保证在多人协同开发之下,代码仍具有良好的维护成本和高效高质的产出。emberjs 秉承约定由于配置的理念,已经在框架层面把应用的最基本结构确定了,开发者只需要顺势而为,即可保证代码的可维护性和可协作性。</p>
<h4 id="数据的双向绑定"><a href="#数据的双向绑定" class="headerlink" title="数据的双向绑定"></a>数据的双向绑定</h4><p>不同于在 react 中,数据只能单向流动,emberjs 中的数据默认是双向的,这意味着数据的变更完全交由框架处理,虽然双向数据绑定在复杂应用中易于导致数据的状态变更不易跟踪和维护,但是对于频繁且复杂的表单处理,双向绑定可以极大的提高开发效率,通过一定的约束和规范,让双向绑定在可控的范围内发生,并不会对维护造成太大的影响。</p>
<h4 id="传统的字符串模板"><a href="#传统的字符串模板" class="headerlink" title="传统的字符串模板"></a>传统的字符串模板</h4><p>对于习惯了传统模板的很多后端程序员来说,字符串模板非常易于接受和理解,几乎没有学习成本,而且能很好的反应 html 结构和视图,这点相对于 react 的 jsx 语法就有很大的不同。这点也足以构成像我这样的后端程序员偏爱 emberjs 的一个理由。</p>
<h4 id="完备的构建工具和构建流程"><a href="#完备的构建工具和构建流程" class="headerlink" title="完备的构建工具和构建流程"></a>完备的构建工具和构建流程</h4><p>emberjs 社区提供了完备的构建工具和构建流程来保证 ember app 的开发和发布效率,基上能完成在多种环境之下的一键开发和部署,非常方便。</p>
<h4 id="渐进式的向下兼容"><a href="#渐进式的向下兼容" class="headerlink" title="渐进式的向下兼容"></a>渐进式的向下兼容</h4><p>前端的发展日新月异,新技术层出不穷,要想一直惠于新技术和新方案的发展,需要不断的升级优化。这也带来了不小的升级维护开销。如果一个框架完全不考虑遗留系统,升级代价巨大,对于人力紧缺,时间宝贵的小团队来说,这样的代价负担不起。emberjs 很好的做到了这一点,基本每个版本都可以做出很平滑的过渡,而不是大规模的修改。在这一点上,angular 就显得非常激进。</p>
<h2 id="emberjs-究竟适合什么样的场景"><a href="#emberjs-究竟适合什么样的场景" class="headerlink" title="emberjs 究竟适合什么样的场景"></a>emberjs 究竟适合什么样的场景</h2><ul>
<li>全栈式解决方案</li>
<li>基于 jquery </li>
<li>约定优于配置</li>
<li>数据的双向绑定</li>
<li>传统的字符串模板</li>
<li>完备的构建工具和构建流程</li>
</ul>
<p>基于这些特点,emberjs 非诚适合于</p>
<ul>
<li>需要一体化解决方案(懒)</li>
<li>不会特别在乎体积,不会特别在乎性能(用的人不是很多,没有时间优化)</li>
<li>需要一个大而全的 css framework,类似bootstrap,semantic-ui,foundation(还是懒)</li>
<li>表单处理复杂(频繁创建和修改,查询)</li>
<li>尽量利用第三方高质量的库来解决现成问题(缺乏足够的时间和精力造轮子)</li>
<li>非专业前端程序员开发和维护(后端程序员开发和发布)</li>
<li>不能花太多时间进行组织架构和代码规范(业务变更频繁,多人交叉开发,缺乏足够的经验和时间进行架构设计,缺前端)</li>
</ul>
<p>具有以上特点的各种复杂的 dashboard 和 内容管理应用。这类应用基本具备性能要求不高,但是需求变更频繁,表单处理复杂,由于频繁变更后端逻辑和数据结构,页面的组织结构和交互需要频繁修改。高频变更之下,要保证代码具有很强的一致性和可维护性,ember 约定优于配置的理念恰如其分得契合这样的需求。</p>
<h2 id="emberjs-的问题"><a href="#emberjs-的问题" class="headerlink" title="emberjs 的问题"></a>emberjs 的问题</h2><p>1.体积庞大</p>
<p>emberjs 的体积非常庞大</p>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Version</th>
<th>Minified Size (gzip)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ember</td>
<td>2.5.0</td>
<td>117.26kb</td>
</tr>
<tr>
<td>Polymer + Web Components Polyfill Lite</td>
<td>1.4.0</td>
<td>54.48kb</td>
</tr>
<tr>
<td>Angular</td>
<td>1.5.0</td>
<td>53.17kb</td>
</tr>
<tr>
<td>React</td>
<td>15.0.2</td>
<td>43.62kb</td>
</tr>
<tr>
<td>Web Components Polyfill</td>
<td>0.7.22</td>
<td>33.66kb</td>
</tr>
<tr>
<td>Vue</td>
<td>1.0.21</td>
<td>25.98kb</td>
</tr>
<tr>
<td>Riot</td>
<td>2.4.1</td>
<td>9.23kb</td>
</tr>
</tbody>
</table>
<p>单从体积上看,emberjs 显得过于雍正,但是在很多场景之下,体积根本不是需要考虑的因素。</p>
<p>2.性能</p>
<p>由于 emberjs 提供了单页应用开发所需要的几乎全部解决方案,相比于像 vuejs、reactjs、riot等这些专注于 UI 层的框架的的性能就有了一定的差距,再加上字符串模板天生特性和 emberjs 实现双向绑定的机制,性能不是很好是 emberjs 的一个问题,但是并不代表 emberjs 没有优化的空间。<a href="http://www.discourse.org/" target="_blank" rel="external">discourse</a> 就是很好的案例。点击查看 discourse 团队对 emberjs 的优化 <a href="https://eviltrout.com/" target="_blank" rel="external">eviltrout</a>。</p>
<p>3.灵活性差</p>
<p>大而全框架天生的缺陷</p>
<p>4.学习曲线陡峭</p>
<p>emberjs 特性众多,开发环境相对复杂,需要较长的时间入门和实践,尤其是对刚接触全栈式前端框架的初学者来说,要很好掌握component 交互、单双数据绑定使用场景、插件开发等需要花一定时间学习。</p>
<p>emberjs 的开发一旦掌握,开发效率会出奇得快。</p>
<h2 id="选择合适的才是最好的"><a href="#选择合适的才是最好的" class="headerlink" title="选择合适的才是最好的"></a>选择合适的才是最好的</h2><p>我们的团队在生产环境中使用了 reactjs、emberjs、vuejs、riot,还有部分 angular 遗留项目。</p>
<p>angular 是最初使用 js framework 时候的一个尝试,生产环境没有做过大规模得实践,现在基本放弃。</p>
<p>riot 是个优秀框架,体积小,性能优越,灵活性极强,也正是这点,在代码量达到一定规模之后,如果架构设计跟不上代码量和复杂性的增长,可维护性会越来越差。riot 的轮子也很少。</p>
<p>react 的出现带来了前端跨越式地发展,也带来了全新的思维方式来构建前端 UI 层,几乎是一个全新生态,正因为如此, react 使用的所有轮子都需要重新打造才能完全发挥 react 带来的优势。还有一点是,JSX 的语法也完全不符合代码即视图的设计理念,需要时间适应。</p>
<p>vue 同样是个十分优秀的框架,接口表现力强,学习成本低,能够很快入门,同样 vue 保留了 html 结构即视图的模板概念,使用起来非常顺手,结合 vue-router 和 vuex,一样可以构建非常复杂的应用。vue 的生态也缺乏足够多的轮子。</p>
<p>对于面向大规模用户、性能要求卓越、灵活性强的、轮子不得不造、多人协作的场景,react 和 vue 是更合适的选择。但是面对类似 dashboard 和 CMS 这种表单处理众多、需求复杂的、变更频繁的业务场景,emberjs 可能是更好的选择。</p>
<ul>
<li>国内第一个 emberjs 讨论区 <a href="http://emberjs-china.org/" target="_blank" rel="external">emberjs-china</a></li>
<li>基于 semant-ui 的 ember addon <a href="https://github.com/wecatch/ember-semantic-ui" target="_blank" rel="external">ember-semantic-ui</a></li>
<li>比 ember-data 简单很多的 ORM <a href="https://github.com/wecatch/ember-easy-orm" target="_blank" rel="external">ember-easy-orm</a></li>
</ul>
<p><a href="http://sanyuesha.com/about/">三月沙</a> <a href="http://sanyuesha.com/2016/06/04/why-emberjs-is-best-for-cms/">原文链接</a> </p>
<h2 id="emberjs-是什么"><a href="#emberjs-是什么" class="headerlink" title="emberjs 是什么"></a>emberjs 是什么</h2><p>在开始之前,先看一组 github 数据</p>
<p><strong>ember.js</strong></p>
<ul>
<li>star 16291</li>
<li>contributors 586</li>
<li>releases 181</li>
<li>issues 167 open, 4397 closed</li>
</ul>
<p><a href="https://github.com/emberjs/ember.js">https://github.com/emberjs/ember.js</a></p>
<p><strong>angular.js</strong></p>
<ul>
<li>star 49643</li>
<li>contributors 1476</li>
<li>releases 169</li>
<li>issues 817 open, 6992 closed</li>
</ul>
<p><a href="https://github.com/angular/angular.js">https://github.com/angular/angular.js</a></p>
<p><strong>vue.js</strong></p>
<ul>
<li>star 19883</li>
<li>contributors 69</li>
<li>releases 137</li>
<li>issues 27 open, 2252 closed</li>
</ul>
<p><a href="https://github.com/vuejs/vue">https://github.com/vuejs/vue</a></p>
<p><strong>react.js</strong></p>
<ul>
<li>star 43026</li>
<li>contributors 713</li>
<li>releases 46</li>
<li>issues 480 open, 2843 closed</li>
</ul>
<p><a href="https://github.com/facebook/react">https://github.com/facebook/react</a></p>
<p><strong>riot.js</strong></p>
<ul>
<li>star 9565</li>
<li>contributors 138</li>
<li>releases 137</li>
<li>issues 96 open, 1089 closed</li>
</ul>
<p><a href="https://github.com/riot/riot">https://github.com/riot/riot</a></p>
<blockquote>
<p>注: 统计时间为本文编写之时</p>
</blockquote>
<p>emberjs 和其他同类框架一样,至少在 github 的 表现足以证明其流行度是不低的,但各个框架的诞生的场景以及解决的问题又不尽相同。</p>
<p>emberjs 是基于 jquery 的一个全栈式前端框架,它提供了从路由层-> view 层->数据和网络交互层的全部解决方案,只用 emberjs 就足以解决前端单页应用遇到的所有复杂问题,而且兼具有不俗的开发效率和开发质量。</p>
<h4 id="全栈式解决方案"><a href="#全栈式解决方案" class="headerlink" title="全栈式解决方案"></a>全栈式解决方案</h4><p>emberjs 是一个大而全的框架,它几乎囊括了单页应用开发所需要的方方面面: component,model,service,route,temlate<br>,ember-data。借助 emberjs 几乎可以完成任何规模的复杂应用,而不用担心它没有提供足够丰富的特性或者需要求助其他第三方库(不一定可以很好的和框架本身集成)。</p>
python的模块加载和路径查找
http://wecatch.me/2016/05/28/python-module-path-find/
2016-05-28T03:27:22.000Z
2016-09-01T03:21:18.000Z
<p><a href="http://sanyuesha.com/about/" target="_blank" rel="external">三月沙</a> <a href="http://sanyuesha.com/2016/05/28/python-module-path-find/" target="_blank" rel="external">原文链接</a> </p>
<p>深入这个问题之前,我们需要理解几个概念:</p>
<p><strong>module</strong>:模块, 一个 <code>py</code>文件或以其他文件形式存在的可被导入的就是一个模块</p>
<p><strong>package</strong>:包,包含有 <code>__init__</code> 文件的文件夹</p>
<p><strong>relative path</strong>:相对路径,相对于某个目录的路径</p>
<p><strong>absolute path</strong>:绝对路径,全路径</p>
<p><strong>路径查找</strong>:python 解释器查找被引入的包或模块</p>
<h2 id="python-执行时是如何查找包和模块的"><a href="#python-执行时是如何查找包和模块的" class="headerlink" title="python 执行时是如何查找包和模块的"></a>python 执行时是如何查找包和模块的</h2><p>1.python 执行一个文件</p>
<p>python 执行一个文件,无论执行的方式是绝对路径还是相对路径,解释器都会把文件所在的目录加入到系统查找路径中,也就是 <code>sys.path</code> 这个 <code>list</code> 中,而 <code>sys.path</code> 又是由系统的 python 环境变量决定的。</p>
<a id="more"></a>
<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="comment">#test.py</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line"><span class="keyword">print</span> sys.path[<span class="number">0</span>]</span><br></pre></td></tr></table></figure>
<p>执行这个文件:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">python test.py</span><br><span class="line">python /Users/x/workspace/blog-code/p2016_05_28_python_path_find</span><br><span class="line">test.py</span><br></pre></td></tr></table></figure>
<p>相对路径和绝对路径输出相同的结果。test.py 所在的文件夹都会被加入 <code>sys.path</code> 的首位,注意这里是首位,也就是索引为0的位置。</p>
<p>解释器执行时,首先搜索 <code>built-in module</code> ,也就是解释器查找模块最先查找的是 <code>built-in module</code> ,其次才会搜索 <code>sys.path</code> 所包含的路径。这样的查找顺序将会引起同名包或模块被遮蔽的问题。</p>
<p>A sample </p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">├── os.py</span><br><span class="line">├── <span class="built_in">test</span>2.py</span><br><span class="line">├── redis.py</span><br></pre></td></tr></table></figure>
<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="comment">#test2.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> redis <span class="keyword">import</span> Redis</span><br></pre></td></tr></table></figure>
<p>执行 <code>test2.py</code> 文件</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"/Users/x/workspace/blog-code/p2016_05_28_python_path_find/test2.py"</span>, line 1, <span class="keyword">in</span> <module></span><br><span class="line"> from redis import Redis</span><br><span class="line">ImportError: cannot import name Redis</span><br></pre></td></tr></table></figure>
<p>由于 <code>os</code> 是 <code>built-in module</code> ,即使在同目录下有同名模块,解释器依然可以找到正确的<code>os</code>模块,而 <code>redis</code> 属于第三方模块,默认安装位置是 python 环境变量中的 <code>site-packages</code> 下,解释器启动之后会将此目录加入 <code>sys.path</code>,按照上面所说的查找顺序,优先在执行文件所在的目录查找,由于其在 <code>sys.path</code> 的首位,因而本地的 <code>redis</code> 被导入。</p>
<p>2.交互式执行环境</p>
<p>进入交互式执行环境,解释器会自动把当前目录加入 <code>sys.path</code>, 这时当前目录是以相对路径的形式出现在 <code>sys.path</code> 中:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">>>> import os.path</span><br><span class="line">>>> import sys</span><br><span class="line">>>> os.path.abspath(sys.path[0])</span><br><span class="line"><span class="string">'/Users/x/workspace/blog-code'</span></span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<p>除此之外,其他与执行一个文件是相同的。</p>
<h2 id="深入-file-变量"><a href="#深入-file-变量" class="headerlink" title="深入 __file__ 变量"></a>深入 <code>__file__</code> 变量</h2><blockquote>
<p><a href="https://docs.python.org/2/reference/datamodel.html" target="_blank" rel="external">python doc</a>: <code>__file__</code> is the pathname of the file from which the module was loaded, if it was loaded from a file. 如果一个模块是从文件加载的,<code>__file__</code> 就是该模块的路径名。</p>
</blockquote>
<p>顾名思义,当模块以文件的形式出现 <code>__file__</code> 指的是模块文件的路径名,以相对路径执行 <code>__file__</code> 是相对路径,以绝对路径执行 <code>__file__</code> 是绝对路径。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#test.py</span></span><br><span class="line"><span class="keyword">print</span> __file__</span><br></pre></td></tr></table></figure>
<p>相对路径执行</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">python <span class="built_in">test</span>3.py</span><br><span class="line"><span class="built_in">test</span>3.py</span><br></pre></td></tr></table></figure>
<p>绝对路径执行</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">python /Users/x/workspace/blog-code/p2016_05_28_python_path_find/<span class="built_in">test</span>3.py</span><br></pre></td></tr></table></figure>
<p>为了保证<code>__file__</code> 每次都能准确得到模块的正确位置,最好再取一次绝对路径 <code>os.path.abspath(__file__)</code>。</p>
<p>进入交互式 shell ,输入<code>__file__</code> 会报错误</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">>>> __file__</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"<input>"</span>, line 1, <span class="keyword">in</span> <module></span><br><span class="line">NameError: name <span class="string">'__file__'</span> is not defined</span><br></pre></td></tr></table></figure>
<p>这是因为当前交互式shell的执行并不是以文件的形式加载,所以不存在<code>__file__</code> 这样的属性。</p>
<h2 id="和-file-相似的-sys-argv-0"><a href="#和-file-相似的-sys-argv-0" class="headerlink" title="和 __file__ 相似的 sys.argv[0]"></a>和 <code>__file__</code> 相似的 sys.argv[0]</h2><p>与 <code>__file__</code> 相似的一个变量是<code>sys.argv[0]</code>,区别是它用来获取主入口执行文件的变量。</p>
<p>A sample </p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#test.py</span></span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">print</span> __file__</span><br><span class="line"><span class="keyword">print</span> sys.argv[<span class="number">0</span>]</span><br></pre></td></tr></table></figure>
<p>以上俩个print输出相同的结果,因为主执行文件和<code>__file__</code>所属的模块是同一个。当我们改变入口文件,区别就出现了。</p>
<p>A sample</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#test.py</span></span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">print</span> __file__</span><br><span class="line"><span class="keyword">print</span> sys.argv[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">#test2.py</span></span><br><span class="line"><span class="keyword">import</span> test</span><br></pre></td></tr></table></figure>
<p>执行 <code>test2.py</code></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/Users/x/workspace/blog-code/p2016_05_28_python_path_find/child/test.py <span class="comment">#__file__</span></span><br><span class="line"><span class="built_in">test</span>2.py <span class="comment">#sys.argv[0]</span></span><br></pre></td></tr></table></figure>
<p>总的来说,<code>sys.argv[0]</code> 是获得入口执行文件路径,<code>__file__</code> 是获得任意模块文件的路径。</p>
<h2 id="sys-modules-的作用"><a href="#sys-modules-的作用" class="headerlink" title="sys.modules 的作用"></a>sys.modules 的作用</h2><p>既然python是在 <code>sys.path</code> 中搜索模块的,那载入的模块存放在何处?答案就是 <code>sys.modules</code>,是存放已载入模块的地方。模块一经载入,python 会把这个模块加入 <code>sys.modules</code> 中供下次载入使用,这样可以加速模块的引入,起到缓存的作用。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">>>> import sys</span><br><span class="line">>>> sys.modules[<span class="string">'tornado'</span>]</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line"> File <span class="string">"<input>"</span>, line 1, <span class="keyword">in</span> <module></span><br><span class="line">KeyError: <span class="string">'tornado'</span></span><br><span class="line">>>> import tornado</span><br><span class="line">>>> sys.modules[<span class="string">'tornado'</span>]</span><br><span class="line"><module <span class="string">'tornado'</span> from <span class="string">'/Users/x/python_dev/lib/python2.7/site-packages/tornado/__init__.pyc'</span>></span><br></pre></td></tr></table></figure>
<p>前面说过python 解释器启动之后,会把预先载入 <code>built-in module</code>,可以通过 <code>sys.modules</code> 验证。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">>>> sys.modules['os']</span><br><span class="line"><module 'os' from '/Users/x/python_dev/lib/python2.7/os.pyc'></span><br><span class="line">>>></span><br></pre></td></tr></table></figure>
<p><strong>获取模块的路径</strong></p>
<p>借助 <code>sys.modules</code>和<code>__file__</code>, 可以动态获取所有已加载模块目录和路径:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">>>> import os</span><br><span class="line">>>> os.path.realpath(sys.modules['os'].__file__)</span><br><span class="line">'/Users/x/python_dev/lib/python2.7/os.pyc'</span><br><span class="line">>>> import tornado</span><br><span class="line">>>> os.path.realpath(sys.modules['tornado'].__file__)</span><br><span class="line">'/Users/x/python_dev/lib/python2.7/site-packages/tornado/__init__.pyc'</span><br></pre></td></tr></table></figure>
<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">get_module_dir</span><span class="params">(name)</span>:</span></span><br><span class="line"> path = getattr(sys.modules[name], <span class="string">'__file__'</span>, <span class="keyword">None</span>)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> path</span><br><span class="line"> <span class="keyword">raise</span> AttributeError(<span class="string">'module %s has not attribute __file__'</span>%name)</span><br><span class="line"> <span class="keyword">return</span> os.path.dirname(os.path.abspath(path))</span><br></pre></td></tr></table></figure>
<h2 id="from-和-import-语句"><a href="#from-和-import-语句" class="headerlink" title="from 和 import 语句"></a>from 和 import 语句</h2><p>知道了 python 是如何搜索模块和保存已导入模块,我们再说说 python 模块的导入</p>
<p>1.<strong>导入顺序</strong></p>
<p>python 包的导入顺序是</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">系统包 --> 同目录 -- > sys.path</span><br></pre></td></tr></table></figure>
<p>因此同名的包,系统包优先级最高 > 同目录 > sys.path,之所以有这样的差别是因为当前执行目录会优先加入<code>sys.path</code> 的首位。</p>
<p>2.导入时执行</p>
<p>当导入一个模块时,模块的顶层变量会被执行,包括全局变量,类以及函数的声明,因此在编写模块时一定要注意消除副作用(函数的调用)。</p>
<p>3.import 语句 和 from 语句</p>
<p>能被 import 的包括:package,package 中的模块,模块中的变量。影响 import 的属性是 <a href="https://docs.python.org/2/tutorial/modules.html?highlight=__all__#importing-from-a-package" target="_blank" rel="external"><code>__all__</code></a>, <code>__all__</code> 是个list,能够影响被 package 中 以 <code>from package import *</code> 被导出的模块,即定义在<code>__all__</code> 中的模块才会被 <code>from package import *</code> 导出。</p>
<h2 id="summary"><a href="#summary" class="headerlink" title="summary"></a>summary</h2><p>python 的模块导入,加载和查找还有很多可以说说的地方,尤其是动态 import, 对应python中的关键字是 <code>__import__</code>,感兴趣的同学可以研究一下 <a href="https://github.com/tornadoweb/tornado/blob/master/tornado/util.py#L127" target="_blank" rel="external"><code>tornado.util</code></a> 模块下的 <code>import_object</code>。</p>
<p>文中所有代码见:<a href="https://github.com/zhyq0826/blog-code/tree/master/p2016_05_28_python_path_find" target="_blank" rel="external">github</a></p>
<p>ps: 转载请注明出处</p>
<p><a href="http://sanyuesha.com/about/">三月沙</a> <a href="http://sanyuesha.com/2016/05/28/python-module-path-find/">原文链接</a> </p>
<p>深入这个问题之前,我们需要理解几个概念:</p>
<p><strong>module</strong>:模块, 一个 <code>py</code>文件或以其他文件形式存在的可被导入的就是一个模块</p>
<p><strong>package</strong>:包,包含有 <code>__init__</code> 文件的文件夹</p>
<p><strong>relative path</strong>:相对路径,相对于某个目录的路径</p>
<p><strong>absolute path</strong>:绝对路径,全路径</p>
<p><strong>路径查找</strong>:python 解释器查找被引入的包或模块</p>
<h2 id="python-执行时是如何查找包和模块的"><a href="#python-执行时是如何查找包和模块的" class="headerlink" title="python 执行时是如何查找包和模块的"></a>python 执行时是如何查找包和模块的</h2><p>1.python 执行一个文件</p>
<p>python 执行一个文件,无论执行的方式是绝对路径还是相对路径,解释器都会把文件所在的目录加入到系统查找路径中,也就是 <code>sys.path</code> 这个 <code>list</code> 中,而 <code>sys.path</code> 又是由系统的 python 环境变量决定的。</p>
wecatch 官方博客开放啦!
http://wecatch.me/2016/05/27/welcome/
2016-05-27T03:27:14.000Z
2016-05-28T00:14:49.000Z
<p>wecatch,独立自由的组织,致力于构建更好的Web产品, 并为开发者和团队提供更敏捷,稳定,灵活的Web解决方案以及专业的咨询服务</p>
<p>本博客致力于分享程序技术,科技生活相关内容,欢迎关注。</p>
<p>wecatch,独立自由的组织,致力于构建更好&#