SegmentFault shomy最新的文章
2017-08-21T12:17:37+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
Python中tuple+=赋值的四个问题
https://segmentfault.com/a/1190000010767068
2017-08-21T12:17:37+08:00
2017-08-21T12:17:37+08:00
shomy
https://segmentfault.com/u/shomy
8
<blockquote>
<p><a href="https://link.segmentfault.com/?enc=FHwmwkezz1r1rL12JRzudg%3D%3D.YbERs%2F0BXPPpXDA8rWjYa3OEalu%2BjkKRagD0zYClJD1aml13iflvrkcERm3KbzpZBluROkoJdkJRlbCarWXFoQ%3D%3D" rel="nofollow">原文链接</a></p>
<p>最近偶尔翻看Fluent Python,遇到有意思的东西就记下来. 下面的是在PyCon2013上提出的一个关于<code>tuple</code>的Augmented Assignment也就是增量赋值的一个问题。 并且基于此问题, 又引申出3个变种问题.</p>
</blockquote>
<h2>问题</h2>
<p>首先看第一个问题, 如下面的代码段:</p>
<pre><code>>>> t = (1,2, [30,40])
>>> t[2] += [50,60]</code></pre>
<p>会产生什么结果呢? 给出了四个选项:</p>
<ol>
<li><p><code>t</code> 变成 <code>[1,2, [30,40,50,60]</code></p></li>
<li><p><code>TypeError is raised with the message 'tuple' object does not support item assignment</code></p></li>
<li><p>Neither 1 nor 2</p></li>
<li><p>Both 1 and 2</p></li>
</ol>
<p>按照之前的理解, <code>tuple</code>里面的元素是不能被修改的,因此会选<code>2</code>. 如果真是这样的话,这篇笔记就没必要了,Fluent Python中也就不会拿出一节来讲了。 正确答案是<code>4</code></p>
<pre><code>>>> t = (1,2,[30,40])
>>> t[2] += [50,60]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])</code></pre>
<p>问题来了,为什么异常都出来了, <code>t</code>还是变了?<br>再看第二种情况,稍微变化一下,将<code>+=</code>变为<code>=</code>:</p>
<pre><code>>>> t = (1,2, [30,40])
>>> t[2] = [50,60]</code></pre>
<p>结果就成酱紫了:</p>
<pre><code>>>> t = (1,2, [30,40])
>>> t[2] = [50,60]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40])</code></pre>
<p>再看第三种情况,只把<code>+=</code>换为<code>extend</code>或者<code>append</code>,:</p>
<pre><code>>>> t = (1, 2, [30,40])
>>> t[2].extend([50,60])
>>> t
(1, 2, [30, 40, 50, 60])
>>> t[2].append(70)
>>> t
(1, 2, [30, 40, 50, 60, 70])
</code></pre>
<p>又正常了,没抛出异常? </p>
<p>最后第四种情况, 用变量的形式:</p>
<pre><code>>>> a = [30,40]
>>> t = (1, 2, a)
>>> a+=[50,60]
>>> a
[30, 40, 50, 60]
>>> t
(1, 2, [30, 40, 50, 60])
>>> t[2] += [70,80]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60, 70, 80])</code></pre>
<p>又是一种情况, 下面就探究一下其中的原因.</p>
<h2>原因</h2>
<p>首先需要重温<code>+=</code>这个运算符,如<code>a+=b</code>:</p>
<ul>
<li><p>对于可变对象(mutable object)如<code>list</code>, <code>+=</code>操作的结果会直接在<code>a</code>对应的变量进行修改,而<code>a</code>对应的地址不变.</p></li>
<li><p>对于不可变对象(imutable object)如<code>tuple</code>, <code>+=</code>则是等价于<code>a = a+b</code> 会产生新的变量,然后绑定到<code>a</code>上而已.</p></li>
</ul>
<p>如下代码段, 可以看出来:</p>
<pre><code>>>> a = [1,2,3]
>>> id(a)
53430752
>>> a+=[4,5]
>>> a
[1, 2, 3, 4, 5]
>>> id(a)
53430752 # 地址没有变化
>>> b = (1,2,3)
>>> id(b)
49134888
>>> b += (4,5)
>>> b
(1, 2, 3, 4, 5)
>>> id(b)
48560912 # 地址变化了</code></pre>
<p>此外还需要注意的是, python中的<code>tuple</code>作为不可变对象, 也就是我们平时说的元素不能改变, 实际上从报错信息<code>TypeError: 'tuple' object does not support item assignment</code>来看, 更准确的说法是指其中的元素不支持赋值操作<code>=</code>(<strong>assignment</strong>).</p>
<p>先看最简单的第二种情况, 它的结果是符合我们的预期, 因为<code>=</code>产生了<code>assign</code>的操作.(在<a href="https://link.segmentfault.com/?enc=SrZNl6m0QtlGaBlDt2no7g%3D%3D.7nwNZfEp1dp9sxPcg9DnJoRCSUONaPu1bwFPx2E7hAOfXUC9rJWebMK82ObDGQfP" rel="nofollow">由一个例子到python的名字空间</a> 中指出了赋值操作<code>=</code>就是创建新的变量), 因此<code>s[2]=[50,60]</code>就会抛出异常. </p>
<p>再看第三种情况,包含<code>extend/append</code>的, 结果tuple中的列表值发生了变化,但是没有异常抛出. 这个其实也相对容易理解. 因为我们知道<code>tuple</code>中存储的其实是元素所对应的地址(id), 因此如果没有赋值操作且tuple中的元素的<code>id</code>不变,即可,而<code>list.extend/append</code>只是修改了列表的元素,而列表本身id并没有变化,看看下面的例子:</p>
<pre><code>>>> a=(1,2,[30,40])
>>> id(a[2])
140628739513736
>>> a[2].extend([50,60])
>>> a
(1, 2, [30, 40, 50, 60])
>>> id(a[2])
140628739513736</code></pre>
<p>目前解决了第二个和第三个问题, 先梳理一下, 其实就是两点:</p>
<ul>
<li><p>tuple内部的元素不支持赋值操作</p></li>
<li><p>在第一条的基础上, 如果元素的<code>id</code>没有变化, 元素其实是可以改变的.</p></li>
</ul>
<p>现在再来看最初的第一个问题: <code>t[2] += [50,60]</code> 按照上面的结论, 不应该抛异常啊,因为在我们看来<code>+=</code> 对于可变对象<code>t[2]</code>来说, 属于<code>in-place</code>操作,也就是直接修改自身的内容, <code>id</code>并不变, 确认下id并没有变化:</p>
<pre><code>>>> a=(1,2,[30,40])
>>> id(a[2])
140628739587392
>>> a[2]+=[50,60]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> a
(1, 2, [30, 40, 50, 60])
>>> id(a[2]) # ID 并没有发生改变
140628739587392
</code></pre>
<p>跟第三个问题仅仅从<code>t[2].extend</code>改成了<code>t[2]+=</code>, 就抛出异常了,所以问题应该是出在<code>+=</code>上了.<br>下面用<code>dis</code>模块看看它俩执行的步骤:<br>对下面的代码块执行<code>dis</code>:</p>
<pre><code>t = (1,2, [30,40])
t[2] += [50,60]
t[2].extend([70, 80])</code></pre>
<p>执行<code>python -m dis test.py</code>,结果如下,下面只保留第2,3行代码的执行过程,以及关键步骤的注释如下:</p>
<pre><code> 2 21 LOAD_NAME 0 (t)
24 LOAD_CONST 1 (2)
27 DUP_TOPX 2
30 BINARY_SUBSCR
31 LOAD_CONST 4 (50)
34 LOAD_CONST 5 (60)
37 BUILD_LIST 2
40 INPLACE_ADD
41 ROT_THREE
42 STORE_SUBSCR
3 43 LOAD_NAME 0 (t)
46 LOAD_CONST 1 (2)
49 BINARY_SUBSCR
50 LOAD_ATTR 1 (extend)
53 LOAD_CONST 6 (70)
56 LOAD_CONST 7 (80)
59 BUILD_LIST 2
62 CALL_FUNCTION 1
65 POP_TOP
66 LOAD_CONST 8 (None)
69 RETURN_VALUE</code></pre>
<p>解释一下关键的语句:</p>
<ul>
<li><p><code>30 BINARY_SUBSCR</code>: 表示将<code>t[2]</code>的值放在TOS(Top of Stack),这里是指<code>[30, 40]</code>这个列表</p></li>
<li><p><code>40 INPLACE_ADD</code>: 表示<code>TOS += [50,60]</code> 执行这一步是可以成功的,修改了TOS的列表为<code>[30,40,50,60]</code></p></li>
<li><p><code>42 STORE_SUBSCR</code>: 表示<code>s[2] = TOS</code> 问题就出在这里了,这里产生了一个<strong>赋值操作</strong>,因此会抛异常!但是上述对列表的修改已经完成, 这也就解释了开篇的第一个问题。</p></li>
</ul>
<p>再看<code>extend</code>的过程,前面都一样,只有这一行:</p>
<ul><li><p><code>62 CALL_FUNCTION</code>: 这个直接调用内置extend函数完成了对原列表的修改,其中并没有<code>assign</code>操作,因此可以正常执行。</p></li></ul>
<p>现在逐渐清晰了, 换句话说,<code>+=</code><strong>并不是原子操作</strong>,相当于下面的两步:</p>
<pre><code>t[2].extend([50,60])
t[2] = t[2]</code></pre>
<p>第一步可以正确执行,但是第二步有了<code>=</code>,肯定会抛异常的。 同样这也可以解释在使用<code>+=</code>的时候,为何<code>t[2]</code>的<code>id</code>明明没有变化,但是仍然抛出异常了。</p>
<p>现在用一句话总结下:</p>
<blockquote><p>tuple中元素不支持<code>assign</code>操作,但是对于那些是可变对象的元素如列表,字典等,在没有<code>assign</code>操作的基础上,比如一些<code>in-place</code>操作,是可以修改内容的</p></blockquote>
<p>可以用第四个问题来简单验证一下,使用一个指向<code>[30,40]</code>的名称<code>a</code>来作为元素的值,然后对<code>a</code>做<code>in-place</code>的修改,其中并没有涉及到对tuple的<code>assign</code>操作,那肯定是正常执行的。</p>
<h2>总结</h2>
<p>这个问题其实以前也就遇到过,但是没想过具体的原理,后来翻书的时候又看到了, 于是花了点时间把这一个系列查了部分资料以及结合自己的理解都整理了出来, 算是饭后茶点吧, 不严谨的地方烦请指出.</p>
<p>部分参考如下:</p>
<ul>
<li><p><a href="https://link.segmentfault.com/?enc=XpaSM7tYV491SadqnBnxgg%3D%3D.QsAasyASmnTLmKBsed5SCwUPWRAql%2F3AyfJPqZ%2FUGX2GVU2AeoXU8R6F185uVhi1" rel="nofollow">python bugs</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=aLQRDulim7YTj%2FNDk%2Fs9Ag%3D%3D.Rj3IjrHDFjxyLC%2FdrKHEz3T4iP%2FsVMe13BmcFUv7pL02FZtJZ64YD1oVqqnWJjvGD5NaG92JDt2kMcOkcgfV3o1496Khf2dehbt33dyeTv3wcmSqXdUbmD7Pf2qRIhnm3l2n%2BGqD93FsPNqnN%2F4L8npNq4jkULpup2b7p9lL4m8%3D" rel="nofollow">python faq</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=1HzZ%2BmQcTAh0MyzMmsjtqg%3D%3D.ND2v0w2X4yUUbIkK8tC6OAkMokKiUmiJrZX5OIa4pJMw2uI07IDgufIOZ3RF7lsbmhjEPUk2rwMyQL%2Fm5S5FCBEp3M4mxXq%2FI7oMq7GuYjqCIAAc7FY9rJWRr%2FWmwzrZOTOgxX6nQtNhZGaBIY2Thd9%2BQtFc1C7wrMLccdVnfkM%3D" rel="nofollow">stackoverflow</a></p></li>
<li><p>Fluent Python</p></li>
</ul>
hexo-admin后台管理博客
https://segmentfault.com/a/1190000010434546
2017-07-31T19:01:12+08:00
2017-07-31T19:01:12+08:00
shomy
https://segmentfault.com/u/shomy
4
<p><a href="https://link.segmentfault.com/?enc=J4W2rvdIvhuiowiO1dzVbQ%3D%3D.pnB2HLCACVu337PyPHVD6iLyEi0pfg5%2BIH%2FPms1h60dbuOpEHYSIrCBdQyTDEe8QcN1UbUXpTCq5rMC98uTZlg%3D%3D" rel="nofollow">原文链接</a></p>
<h2>需求</h2>
<p>像Hexo这种静态博客用起来很方便,但是硬伤就是没有后台管理,每次想写博客或者说想要修改原博客的话,都需要在本机上找markdown文件,修改之后发布,尤其是如果机器上没有安装hexo,git等工具的时候,就无法修改了。因此hexo可不可以以像wp,ghost这种有后台管理, 随时随地编辑博客呢?</p>
<p>正巧hexo专门有一个插件<a href="https://link.segmentfault.com/?enc=5icYOCVZTgjSsCDOouc6Ww%3D%3D.FUYUB77nYdGjWoMyNJ8IZ1DQwhXtci46OU%2B6HfK1CMKtciRbsd6iX8iElTzwDGz%2F" rel="nofollow">hexo-admin</a> 提供了在web UI下增删改查博客的功能,我也正好有一个VPS, 于是就有了下面的一种方法.</p>
<h2>解决方法</h2>
<p>首先说一下条件与需求。 我自己有一个的VPS,本来是可以直接将hexo部署到VPS的,但是考虑到自己维护的VPS的稳定性等问题,最后决定将hexo的静态页面部署github-pages上。 </p>
<p>我的目的有两个:</p>
<ul>
<li><p>拥有在线编辑博客的后台</p></li>
<li><p>本地机器同时也可以编辑发布博客</p></li>
<li><p>二者需要保持同步</p></li>
</ul>
<p>基于这样的需求,仅仅靠github pages显然不可行了,必须借助VPS了.保持同步的意思是VPS的内容与本地机器上的博客的资源要保持一致。就是说我在线修改了某篇博客,然后之后如果可以很简单的同步到本地,使得两端保持同步,反过来一样。</p>
<p>我的做法是:</p>
<ul>
<li><p>将hexo目录下的相关文件包括_config.yml,source/等文件建立一个私人repo(个人使用coding.net)</p></li>
<li><p>VPS上安装好hexo,具体过程与换台机器安装同理, 可参考这篇博客<a href="https://link.segmentfault.com/?enc=OGRr07YJzrrDJQGTzdrGhg%3D%3D.inalY%2BeVVEa9RDviLHPsTgwfBLIln5TRJxXmJNqyqibgJH5BMwQl9P%2Bd3a9E8mx2JsG8m1LLxuRCDAevJ7xzzA%3D%3D" rel="nofollow">hexo迁移重装</a>。</p></li>
<li><p>VPS上安装hexo-admin插件,这样可以在线编辑了. 在这一步的时候, 为了方便,我用了一个子域名以及在VPS上用nginx做前段端口转发.</p></li>
</ul>
<p>到这里后台管理基本没有问题了. 接下来就是在线发布了. 正巧的是hexo-admin提供了这个功能, 有一个Deploy的功能,具体实现是使用nodejs的<code>spawn</code>函数来执行发布脚本的. 一开始不太懂<code>spawn</code>这个函数的使用, 就直接在deploy中随便输入了东西,结果各种 spawn err. 后来查过资料,才知道deploy的输入的位置其实是对<code>hexo-admin</code>的配置里面<code>deployCommand</code>的选项,可以为空. 打个比方,如果我在<code>deployCommand</code>写的是<code>./hexo-deploy</code>, 然后点击deploy按钮之后, 后台就可以执行<code>hexo-deploy</code>脚本的内容了. 有了这个接口就非常灵活了. 因为基于此, 我们不但来发布博客,甚至可以把同步的脚本加进去.具体参考 <a href="https://link.segmentfault.com/?enc=yaSGF7Pkhz5vA018eX3Ykg%3D%3D.RA2kojbhrCsnlTTb1l%2FPWPhSwe0JF3gi5SLLNZ8jd6bUyE%2FENDx7J7Ff5LNXkswa" rel="nofollow">https://github.com/jaredly/he...</a></p>
<p>下面说一下发布以及同步的操作:</p>
<ul>
<li><p>在博客目录写一个有关git同步的小脚本:<code>server-upload.sh</code> 用来同步VPS目录下的变化,主要代码其实就是先<code>git pull</code> 拉取repo最新, 之后在push. 具体脚本代码不再赘述.</p></li>
<li>
<p>建一个<code>hexo-deploy</code>的脚本,内容:</p>
<pre><code>hexo g && hexo d
./server-upload.sh</code></pre>
</li>
<li>
<p>利用hexo-admin的deploy功能,可以在线发布,注意修改<code>_config.yml</code>: 加上hexo-admin的admin选项, 加一个<code>deployCommand: ./hexo-deploy</code>的字段,如下:</p>
<pre><code>admin:
username: XXX
password: XXXXX
deployCommand: './hexo-deploy'</code></pre>
</li>
<li><p>编辑完博客时候,发布的时候,需要点击hexo-admin的deploy。这样在后台就可以执行前面定义的<code>hexo-deloy</code>脚本了</p></li>
<li><p>本地机器也需要有一个<code>upload.sh</code>的脚本, 每次同样需要先git pull 在git push 到私人repo. 其实就是版本控制那一套.</p></li>
</ul>
<p>到现在基本完成了自己可以随时随地编辑博客并且多端同步的需求了. 之前也考虑过CI持续集成,但是大部分都是再建立一个分支, 需要把博客的source公开, 不太合适. 所以就选择了hexo-admin这种方法. </p>
<p>这篇博客便是在线完成的.<br><img src="/img/remote/1460000010434549" alt="" title=""></p>
<p>其实这里面的坑还是蛮多的(主要不熟悉node). 此外开始使用supervisor做后台运行监控, 结果不知为何, 在<code>hexo d</code>包括git的pull或者push等操作,都会报错,提示让我确认git用户, 但是我已经设置了<code>git config</code>的email和name. 查了很多资料也没有解决, 后来直接抛弃supervisor, 直接使用<code>nohup</code>来放后台运行了. 大概是这样了, 有什么问题可以留言或者邮件.</p>
Hexo下mathjax的转义问题
https://segmentfault.com/a/1190000007261752
2016-10-24T15:37:45+08:00
2016-10-24T15:37:45+08:00
shomy
https://segmentfault.com/u/shomy
3
<blockquote>源自<a href="https://link.segmentfault.com/?enc=0NfmfUhbzmpoALG6YXj7lQ%3D%3D.pz%2FYc9MqC230r0xZ9JY3fZR8OFdeLkpI6Ml11Ug8VHTM%2B4FIB7NbAJbEYeE8ZEO3k%2F1LOdqV0%2BiN%2F0pI%2B8FevQ%3D%3D" rel="nofollow">我的博客</a>
</blockquote>
<h2>问题</h2>
<p>我们平时使用markdown写文档的时候,免不了会碰到数学公式,好在有强大的<a href="https://link.segmentfault.com/?enc=09IGPNW2Bsm73khLuVfAww%3D%3D.14xMr6DUe9vPvrWChfnRu3tbhzxOKVuMCMqQBNOFMf0%3D" rel="nofollow">Mathjax</a>,可以解析网页上的数学公式,与hexo的结合也很简单,可以手动加入js,或者直接使用<a href="https://link.segmentfault.com/?enc=8XSrhHoGlZICM0Y6YfhfHg%3D%3D.%2FwGRvIF%2B8Y207MmYTChx8h6%2FiIv19%2B2e1QeZg%2FI002NBBt5%2B%2FJGmFbDnOIsBT7Yk" rel="nofollow">hexo-math</a>插件.大部分情况下都是可以的,但是Markdwon本身的特殊符号与Latex中的符号会出现冲突的时候:</p>
<ul>
<li>
<code>—</code>的转义,在markdown中,<code>_</code>是斜体,但是在latex中,却有下标的意思,就会出现问题。</li>
<li>\\的换行,在markdown中,\\会被转义为\,这样也会影响影响mathjax对公式中的\\进行渲染</li>
</ul>
<h2>原因</h2>
<p>hexo默认使用<code>marked.js</code>去解析我们写的markdown,比如一些符号,<code>_</code>代表斜体,会被处理为<code><em></code>标签,<br>比如<code>x_i</code>在开始被渲染的时候,处理为<code>x<em>i</em></code>,这个时候mathjax就无法渲染成下标了。<br>很多符号都有这个问题,比如粗体<code>*</code>,也是无法在mathjax渲染出来的,好在有替代的乘法等,包括<code>\\</code>同理。<br>所以说到底,是hexo使用的markdown引擎的锅,因为很多其它引擎在这方面处理的很好。</p>
<h2>解决方法</h2>
<p>在网上查了一写资料,总结为如下的方法</p>
<h3>手动escape</h3>
<p>这个方法最直接,需要转义我就转义。比如我需要在公式中写下标符号,那就修改写法写为: <code>$x\_i$</code>;需要换行就使用<code>\\\\</code>,即可。<br>很明显,这种方式虽然可以解决问题通用性很差,比如想迁移到其它地方,就无法识别了,因为大部分的markdown引擎是没有这个问题的。</p>
<h3>保护代码块</h3>
<p>我们知道在markdown中的两个`之间的东西不会转义,正好符号我们的需求,我们保护$$之间的代码不被markdown渲染,这样就可以,但是有一个后遗症,就是的样式问题, markdown中是code的样式的,这样也还是有问题,解决思路就是重新处理code标签,使其遇见$$的话,跳过,详情见:<a href="https://link.segmentfault.com/?enc=XoeBzGbwpYVmpzEVn8YBEA%3D%3D.izz1xGK1e464a8%2FC09CfwsFVAcoedJVjKuM6oOgb4EUdGksoJPce7kfcWsBRTXx4wVhjBMZ6ACzm4TOvjn%2FZYsEHe2ar0nGTdNiO8NOBLRg%3D" rel="nofollow">解决 MathJax 与 Markdown 的冲突</a><br>这个方法有个问题,就是有时候我们的代码中会有$$的出现,这时候,仍然使用mathjax就有可能出现无法预料的结果了。</p>
<h3>更换Hexo的markdown引擎</h3>
<p>这个是我目前使用的方法,直接换发动机,就是把hexo默认的渲染markdown的引擎换掉。查到了有如下几个插件可以使用:</p>
<ul>
<li>
<a href="https://link.segmentfault.com/?enc=yl6ucGBmMUsKwiDoYrnxnw%3D%3D.bw%2B7O1i%2FvshN5S0m3NRMI3OiajfkUIOAvw29SZ6Oy%2B2Fc5FwcQ31hCkxOT2g7C4L" rel="nofollow">hexo-renderer-pandoc</a>, 很强大的解析器,Pandoc的语法完全没有上述问题。</li>
<li>
<a href="https://link.segmentfault.com/?enc=qnWonpuEHUFK2bd%2BZuCMRw%3D%3D.JJu21JWf2m77xUyr9%2FuvFFaPXtclbrf2PESE0qA5%2FsnvZodv3ZhaXKUpUmBat5DL" rel="nofollow">hexo-renderer-kramed</a>,或者<a href="https://link.segmentfault.com/?enc=jIDE5lsSJvwb%2BVsj5hYPpQ%3D%3D.P%2FeqMpv37zEMYDPb7ENtBOUHnSlVtxPLkG9WOA7Jv%2FW5pbxru50xh7S6F9cUBsxq" rel="nofollow">hexo-renderer-marked</a>,这两个差不多,第一个fork了第二个,改了一部分bug,但是还是有部分问题。</li>
</ul>
<p>下面说一下如何使用pandoc渲染。</p>
<ol>
<li>首先安装Pandoc,官网提供了Ubuntu的deb安装包,按照官网教程就可以安装完成。</li>
<li>
<p>就是卸载hexo默认的markd,再安装新的:</p>
<pre><code>npm uninstall hexo-renderer-marked --save
npm install hexo-renderer-pandoc --save</code></pre>
</li>
</ol>
<p>如果决定要换的话,还是更换为Pandoc吧,虽然比较笨重,需要首先安装Pandoc, 不过的确可以完美解决上述的不兼容问题,虽然它的语法与markdown有些微的差异,不过常用的几乎都一样。</p>
<h3>修改Hexo渲染源码</h3>
<p>这种方法,相对来说,通用性较高的一种方式。思路就是修改hexo的渲染源码: <code>nodes_modules/lib/marked/lib/marked.js</code>:</p>
<ul>
<li>去掉<code>\\</code>的额外转义</li>
<li>将em标签对应的符号中,去掉<code>_</code>,因为markdown中有<code>*</code>可以表示斜体,<code>—</code>就去掉了。</li>
</ul>
<p>具体思路参考了<a href="https://link.segmentfault.com/?enc=sS7hGIsttkvC0b1%2BstQUGQ%3D%3D.gW8HFc0nKtAUqh0XnJFw7NfxSTBzYf2vShBqUpGCmuyPPuE66qGOmFyIy2EYS04onqPFet7AyYUmRa%2FOKG%2Fpdw%3D%3D" rel="nofollow">使Marked.js与MathJax共存</a>, 打开<code>nodes_modules/marked/lib/marked.js</code>:<br>第一步: 找到下面的代码:</p>
<pre><code>escape: /^\\([\\`*{}\[\]()# +\-.!_>])/,</code></pre>
<p>改为:</p>
<pre><code>escape: /^\\([`*{}\[\]()# +\-.!_>])/,</code></pre>
<p>这样就会去掉<code>\\</code>的转义了。<br>第二步: 找到em的符号:</p>
<pre><code>em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,</code></pre>
<p>改为:</p>
<pre><code>em:/^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,</code></pre>
<p>去掉<code>_</code>的斜体含义,这样就解决了。为什么说通用性很高,因为我们没有修改文章的内容,可以放到别的引擎下也会顺利渲染,不过可能会遇到一些未知也需要转义的字符,每次都修改。<br>建议使用最后两种方法。</p>
利用chrome扩展使用HTTPS访问google
https://segmentfault.com/a/1190000004865243
2016-04-04T22:35:53+08:00
2016-04-04T22:35:53+08:00
shomy
https://segmentfault.com/u/shomy
0
<blockquote><p>源自<a href="https://link.segmentfault.com/?enc=v2Xgdrl0v6RapDdFuyuMzQ%3D%3D.kzJsdahie03oilPO2Q7nUVzGi5gEqWcGH8sWJZTNhZduWXF35q%2BOtRLUfTFQhJdBsrW1Mv7%2FcV8lACHOnbGbfA%3D%3D" rel="nofollow">我的博客</a></p></blockquote>
<p>在国内访问<code>google</code>的时候,总是需要费一番周折,之前一直修改hosts,可以访问<code>https://google.com.hk</code>,后来发现总是出现连接被重置的情况,如下图:<br><img src="http://7xotye.com1.z0.glb.clouddn.com/google.png" alt="" title=""></p>
<p>而且可以发现,连接是<code>http://google.com.hk</code>, 如果改为<code>https://google.com.hk</code>还是可以正常访问的。但是每次手动修改就太麻烦了,因此想到可以用chrome的扩展程序来做这件事情。查阅了一些文档,发现chrome提供了很直接的接口,就是<code>chrome.tabs.update</code>方法,就是可以修改重定向的。主要代码如下:</p>
<p><code>manifest.json</code>的代码:</p>
<pre><code>{
"manifest_version": 2,
"name": "http2https",
"description": "visit google with https",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png"
},
"background":{
"page":"runjs.html"
},
"permissions":[
"tabs"
]
}</code></pre>
<p><code>js</code>的代码</p>
<pre><code>var urlHistoryMap = [];
chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
if(!/http:\/\/.*?google(dservices)?.com/.test(tab.url)) return;
if(info.status == "loading") {
var preUrl = urlHistoryMap[tabId];
if(null == preUrl || preUrl != tab.url){
chrome.tabs.update(tab.id, {url: tab.url.replace("http://", "https://"),highlighted:true});
urlHistoryMap[tabId] = tab.url;
}
}
});</code></pre>
<p>完整的代码:<a href="https://link.segmentfault.com/?enc=abqWdWYZUd3QqDq5kK2ahw%3D%3D.csRLR4wUyOsZwjn1jtly7g9iJqZk%2F15isDkRqjX%2FJPeCWabdfE38MNBSW5A1tG3J" rel="nofollow">http2https</a></p>
记录Ubuntu & Windows下安装PyV8
https://segmentfault.com/a/1190000004586828
2016-03-11T16:59:23+08:00
2016-03-11T16:59:23+08:00
shomy
https://segmentfault.com/u/shomy
1
<blockquote><p>源自<a href="https://link.segmentfault.com/?enc=n8ZksACxyp5oOwtEwhAkBA%3D%3D.BVwmyPxQRwSG5YLeAeJcF0FynW8MmLccUnVbgcj0G6EC2uU0v2hvflW7sUBJbRRE" rel="nofollow">我的博客</a></p></blockquote>
<hr>
<blockquote>
<p>注:原文有更新</p>
<p>这几天需要在使用<code>PyV8</code>来进行<code>python</code>与<code>javascript</code>的交互。之前在<code>window</code>下安装过,也没有遇到什么问题。</p>
</blockquote>
<p>结果这次在<code>Ubuntu</code>安装遇到了不少坑--主要是网上的办法都不可行,不知道为啥。最终折腾了一晚上,终于好了。</p>
<h2>windows下的安装</h2>
<p>在windows下安装要简单很多,如果有了<code>c++</code>环境之后,只需要在<a href="https://link.segmentfault.com/?enc=JiIIHE8NtQ4kOWBl6V4plQ%3D%3D.rwYbASJb4MYgd3TYZC7HmzFGlZFbirRntSOi08EyBfNmOfSscBTGvLyLfGMi84oB" rel="nofollow">pyv8</a>下载适合自己系统以及适合自己python版本的<code>exe</code>文件即可,然后直接安装就好了,相对来说简单一些</p>
<h2>在Ubuntu下安装</h2>
<h3>安装依赖</h3>
<p>首先安装依赖:<code>Boost</code>, 这一步网上的大部分教程都差不多,也是必须的;</p>
<pre><code>sudo apt-get install scons
sudo apt-get install libboost-dev libboost-thread-dev
sudo apt-get install libboost-system-dev libboost-python-dev
</code></pre>
<h3>安装PyV8</h3>
<p>网上的大部分教程均是使用<code>svn</code>checkout出V8,PyV8的代码,然后再设置什么V8的目录为<code>V8_HOME</code>最后在编译<code>PyV8</code>;先不说能不能使用<code>svn</code>迁出,(反正我翻墙都没有迁出来),后来去google.code网站上, 下载了源代码,结果编译期间出各种错误,根本没办法执行。 <br>还有一些直接使用<code>pip</code>安装的教程,也是报各种错,缺少这个啦,缺少那个啦。<br>于是就想直接找到二进制文件,直接拷到python的包目录得了。结果这条路还算顺畅!最终比较轻松的安装成功了。<br>二进制文件googlecode官网有,有人在github上也提供了很全面的二进制安装文件<a href="https://link.segmentfault.com/?enc=8HVN5V3L63u8usPqcpKe3Q%3D%3D.aG0R3LsrazCyy5K3EgKzhM6K3hjyADZkKURxTiKpkU7OQkjDFVNY2T%2FYBoj3wORu" rel="nofollow">https://github.com/emmetio/pyv8-binaries</a> 找到适合你机器的版本下载.<br>解压出来应该有如下两个文件:一个源码,一个二进制文件。一般Python的第三方包有这俩文件就够了.</p>
<pre><code>PyV8.py
_PyV8.so</code></pre>
<p>然后把这两个文件拷到你的python第三方包的目录里面就好,比如我的是:</p>
<pre><code>cd pyv8 //进入解压后的目录
sudo cp * /usr/lib/python2.7/dist-packages/ </code></pre>
<p>之后尝试导入<code>import PyV8</code>结果报以下的一个错误:</p>
<pre><code>ImportError: libboost_python_py27.so.1.54.0: cannot open shared object file: No such file or directory</code></pre>
<p>当时看到这个错误,然后想到我之前已经安装好了依赖呢,为啥还会报这个错误呢。<br>大致查了一下,有的说是链接库路径设置问题,之后检查了一下<code>libboost_python_py27.so.1.54.0</code></p>
<pre><code>shomy@LiuPC:blog$ find /usr/lib/ -name libboost_python-py27.so.1.54.0
/usr/lib/x86_64-linux-gnu/libboost_python-py27.so.1.54.0</code></pre>
<p>果然有这个链接库,那为啥检测不到呢,问题就出在环境变量<code>LD_LIBRARY_PATH</code>上,因为python寻找链接库的时候,是从该路径里面找的。<br>然而我<code>echo $LD_LIBRARY_PATH</code>却啥都没有,只要把上面的路径加入到环境变量就可以吧。--于是在<code>.bashrc</code>最后加入了一行:(路径可能不一致,请根据自己的实际情况添加)</p>
<pre><code>export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu</code></pre>
<p>保存退出之后,再用<code>source ~/.bashrc</code> 重载一下就好了。再一次<code>import PyV8</code> 正常!<br>执行下面的测试代码:</p>
<pre><code>import PyV8
ctxt = PyV8.JSContext()
ctxt.enter()
func = ctxt.eval("""
(function(){
function hello(){
return "Hello world.";
}
return hello();
})
""")
print func()</code></pre>
<p>执行输出<code>Hello World</code> 就说明安装成功了。可以去模拟登录了~~~</p>
python迭代器与生成器小结
https://segmentfault.com/a/1190000004554823
2016-03-07T13:34:38+08:00
2016-03-07T13:34:38+08:00
shomy
https://segmentfault.com/u/shomy
5
<blockquote>
<p>2016.3.10关于例子解释的补充更新</p>
<p>源自<a href="https://link.segmentfault.com/?enc=dOakKdW%2F0rW1q5jDdWViig%3D%3D.PiwWC3oiv9YAs%2B%2F8dFi2NYMLmjetoenLaE0M%2FkxrD1Ch6peZBfTZ5oDnvjewJyiG6AEp8K1%2FmS9mqQrwEB%2BxRA%3D%3D" rel="nofollow">我的博客</a></p>
</blockquote>
<h3>例子</h3>
<p>老规矩,先上一个代码:</p>
<pre><code>def add(s, x):
return s + x
def gen():
for i in range(4):
yield i
base = gen()
for n in [1, 10]:
base = (add(i, n) for i in base)
print list(base)</code></pre>
<p>这个东西输出可以脑补一下, 结果是<code>[20,21,22,23]</code>, 而不是<code>[10, 11, 12, 13]</code>。 当时纠结了半天,一直没搞懂,后来<a href="https://link.segmentfault.com/?enc=OZ1%2BYYsZ9tBMXemAxdCgew%3D%3D.C4VXbZ8HIlzbvZD3U6Tz5TSJGgnqvQXHaUINXEeCHs8%3D" rel="nofollow">齐老师</a>稍微指点了一下, 突然想明白了--真够笨的,唉。。好了--正好趁机会稍微小结一下python里面的生成器。</p>
<h3>迭代器(iterator)</h3>
<blockquote><p>要说生成器,必须首先说迭代器</p></blockquote>
<h4>区分iterable,iterator与itertion</h4>
<p>讲到迭代器,就需要区别几个概念:<code>iterable</code>,<code>iterator</code>,<code>itertion</code>, 看着都差不多,其实不然。下面区分一下。</p>
<ul>
<li><p><code>itertion</code>: 就是<code>迭代</code>,一个接一个(one after another),是一个通用的概念,比如一个循环遍历某个数组。</p></li>
<li>
<p><code>iterable</code>: 这个是<code>可迭代对象</code>,属于python的名词,范围也很广,可重复迭代,满足如下其中之一的都是<code>iterable</code>:</p>
<ul>
<li><p>可以<code>for</code>循环: <code>for i in iterable</code></p></li>
<li><p>可以按<code>index</code>索引的对象,也就是定义了<code>__getitem__</code>方法,比如<code>list,str</code>;</p></li>
<li><p>定义了<code>__iter__</code>方法。可以随意返回。</p></li>
<li><p>可以调用<code>iter(obj)</code>的对象,并且返回一个<code>iterator</code></p></li>
</ul>
</li>
<li>
<p><code>iterator</code>: <code>迭代器对象</code>,也属于python的名词,只能迭代一次。需要满足如下的<code>迭代器协议</code></p>
<ul>
<li><p>定义了<code>__iter__</code>方法,但是必须<strong>返回自身</strong></p></li>
<li><p>定义了<code>next</code>方法,在python3.x是<code>__next__</code>。<strong>用来返回下一个值</strong>,并且当没有数据了,抛出<code>StopIteration</code></p></li>
<li><p>可以保持当前的状态</p></li>
</ul>
</li>
</ul>
<p>首先<code>str和list</code>是<code>iterable</code> 但不是<code>iterator</code>:</p>
<pre><code>In [3]: s = 'hi'
In [4]: s.__getitem__
Out[4]: <method-wrapper '__getitem__' of str object at 0x7f9457eed580>
In [5]: s.next # 没有next方法
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-5-136d3c11be25> in <module>()
----> 1 s.next
AttributeError: 'str' object has no attribute 'next'
In [6]: l = [1,2] # 同理
In [7]: l.__iter__
Out[7]: <method-wrapper '__iter__' of list object at 0x7f945328c320>
In [8]: l.next
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-c6f8fb94c4cd> in <module>()
----> 1 l.next
AttributeError: 'list' object has no attribute 'next'
In [9]: iter(s) is s #iter() 没有返回本身
Out[9]: False
In [10]: iter(l) is l #同理
Out[10]: False</code></pre>
<p>但是对于<code>iterator</code>则不一样如下, 另外<code>iterable</code>可以支持多次迭代,而<code>iterator</code>在多次<code>next</code>之后,再次调用就会抛异常,只可以迭代一次。</p>
<pre><code>In [13]: si = iter(s)
In [14]: si
Out[14]: <iterator at 0x7f9453279dd0>
In [15]: si.__iter__ # 有__iter__
Out[15]: <method-wrapper '__iter__' of iterator object at 0x7f9453279dd0>
In [16]: si.next #拥有next
Out[16]: <method-wrapper 'next' of iterator object at 0x7f9453279dd0>
In [20]: si.__iter__() is si #__iter__返回自己
Out[20]: True</code></pre>
<p>这样,由这几个例子可以解释清楚这几个概念的区别。</p>
<h4>自定义iterator 与数据分离</h4>
<p>说到这里,迭代器对象基本出来了。下面大致说一下,如何让自定义的类的对象成为迭代器对象,其实就是定义<code>__iter__</code>和<code>next</code>方法:</p>
<pre><code>In [1]: %paste
class DataIter(object):
def __init__(self, *args):
self.data = list(args)
self.ind = 0
def __iter__(self): #返回自身
return self
def next(self): # 返回数据
if self.ind == len(self.data):
raise StopIteration
else:
data = self.data[self.ind]
self.ind += 1
return data
## -- End pasted text --
In [9]: d = DataIter(1,2)
In [10]: for x in d: # 开始迭代
....: print x
....:
1
2
In [13]: d.next() # 只能迭代一次,再次使用则会抛异常
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
----> 1 d.next()
<ipython-input-1-c44abc1904d8> in next(self)
10 def next(self):
11 if self.ind == len(self.data):
---> 12 raise StopIteration
13 else:
14 data = self.data[self.ind]
</code></pre>
<p>从<code>next</code>函数中只能向前取数据,一次取一个可以看出来,不过不能重复取数据,那这个可不可以解决呢?</p>
<p>我们知道<code>iterator</code>只能迭代一次,但是<code>iterable</code>对象则没有这个限制,因此我们可以把<code>iterator</code>从数据中分离出来,分别定义一个<code>iterable</code>与<code>iterator</code>如下:</p>
<pre><code>class Data(object): # 只是iterable:可迭代对象而不iterator:迭代器
def __init__(self, *args):
self.data = list(args)
def __iter__(self): # 并没有返回自身
return DataIterator(self)
class DataIterator(object): # iterator: 迭代器
def __init__(self, data):
self.data = data.data
self.ind = 0
def __iter__(self):
return self
def next(self):
if self.ind == len(self.data):
raise StopIteration
else:
data = self.data[self.ind]
self.ind += 1
return data
if __name__ == '__main__':
d = Data(1, 2, 3)
for x in d:
print x,
for x in d:
print x,
</code></pre>
<p>输出就是:</p>
<pre><code>1,2,3
1,2,3</code></pre>
<p>可以看出来数据可以复用,因为每次都返回一个<code>DataIterator</code>,但是数据却可以这样使用,这种实现方式很常见,比如<code>xrange</code>的实现便是这种数据与迭代分离的形式,但是很节省内存,如下:</p>
<pre><code>In [8]: sys.getsizeof(range(1000000))
Out[8]: 8000072
In [9]: sys.getsizeof(xrange(1000000))
Out[9]: 40</code></pre>
<p>另外有个小tips, 就是为什么可以使用for 迭代迭代器对象,原因就是<code>for</code>替我们做了<code>next</code>的活,以及接收<code>StopIteration</code>的处理。</p>
<p>迭代器大概就记录到这里了,下面开始一个特殊的更加优雅的迭代器: 生成器</p>
<h3>生成器(generator)</h3>
<blockquote><p>首先需要明确的就是生成器也是<code>iterator</code>迭代器,因为它遵循了迭代器协议.</p></blockquote>
<h4>两种创建方式</h4>
<h5>包含<code>yield</code>的函数</h5>
<p>生成器函数跟普通函数只有一点不一样,就是把 <code>return</code> 换成<code>yield</code>,其中<code>yield</code>是一个语法糖,内部实现了迭代器协议,同时保持状态可以挂起。如下:<br>记住一点,<code>yield</code>是数据的生产者,而诸如<code>for</code>等是数据的消费者。</p>
<pre><code>def gen():
print 'begin: generator'
i = 0
while True:
print 'before return ', i
yield i
i += 1
print 'after return ', i
a = gen()
In [10]: a #只是返回一个对象
Out[10]: <generator object gen at 0x7f40c33adfa0>
In [11]: a.next() #开始执行
begin: generator
before return 0
Out[11]: 0
In [12]: a.next()
after return 1
before return 1
Out[12]: 1</code></pre>
<p>首先看到<code>while True</code> 不必惊慌,它只会一个一个的执行~<br>看结果可以看出一点东西:</p>
<ul>
<li><p>调用<code>gen()</code>并没有真实执行函数,而是只是返回了一个生成器对象</p></li>
<li><p>执行第一次<code>a.next()</code>时,才真正执行函数,执行到<code>yield</code>一个返回值,然后就会挂起,保持当前的名字空间等状态。然后等待下一次的调用,从<code>yield</code>的下一行继续执行。</p></li>
</ul>
<p>还有一种情况也会执行生成器函数,就是当检索生成器的元素时,如<code>list(generator)</code>, 说白了就是当需要数据的时候,才会执行。</p>
<pre><code>In [15]: def func():
....: print 'begin'
....: for i in range(4):
....: yield i
In [16]: a = func()
In [17]: list(a) #检索数据,开始执行
begin
Out[17]: [0, 1, 2, 3]
</code></pre>
<p><code>yield</code>还有其他高级应用,后面再慢慢学习。</p>
<h4>生成器表达式</h4>
<p>列表生成器十分方便:如下,求10以内的奇数:<br><code>[i for i in range(10) if i % 2]</code></p>
<p>同样在<code>python 2.4</code>也引入了<code>生成器表达式</code>,而且形式非常类似,就是把<code>[]</code>换成了<code>()</code>.</p>
<pre><code>In [18]: a = ( i for i in range(4))
In [19]: a
Out[19]: <generator object <genexpr> at 0x7f40c2cfe410>
In [20]: a.next()
Out[20]: 0</code></pre>
<p>可以看出生成器表达式创建了一个生成器,而且生有个特点就是<code>惰性计算</code>, 只有在被检索时候,才会被赋值。<br>之前有篇文章:<a href="https://link.segmentfault.com/?enc=O1%2F92m3OFsPtQRMhmu1RTg%3D%3D.X0As1%2BVrlXD4qS3lAIuurnhDcJE0epEGjTjrYLQpB5VEsdjPZQg5Wa%2BFXbHeRok3vmKi%2B4P1Q4JtmYkCQpjF9A%3D%3D" rel="nofollow">python 默认参数问题及一个应用</a>,最后有一个例子:</p>
<pre><code>def multipliers():
return (lambda x : i * x for i in range(4)) #修改成生成器
print [m(2) for m in multipliers()]</code></pre>
<p>这个就是说,只有在执行<code>m(2)</code>的时候,生成器表达式里面的<code>for</code>才会开始从0循环,然后接着才是<code>i * x</code>,因此不存在那篇文章中的问题.<br><strong>惰性计算</strong>这个特点很有用,上述就是一个应用,<a>2gua</a>这样说的:</p>
<blockquote><p>惰性计算想像成水龙头,需要的时候打开,接完水了关掉,这时候数据流就暂停了,再需要的时候再打开水龙头,这时候数据仍是接着输出,不需要从头开始循环</p></blockquote>
<p>个人理解就是就是可以利用生成器来作为数据管道使用,当被检索的时候,每次拿出一个数据,然后向下面传递,传到最后,再拿第二个数据,在下面的例子中会详细说明。<br>其实本质跟迭代器差不多,不一次性把数据都那过来,需要的时候,才拿。</p>
<h2>回到例子</h2>
<p>看到这里,开始的例子应该大概可以有点清晰了,<br>核心语句就是:</p>
<pre><code>def gen():
for i in range(4):
yield i
for n in [1, 10]:
base = (add(i, n) for i in base)</code></pre>
<blockquote><p><strong>之前的解释有点瑕疵,容易误导对生成器的理解</strong>:<br>在执行<code>list(base)</code>的时候,开始检索,然后生成器开始运算了。关键是,这个循环次数是2,也就是说,有两次生成器表达式的过程。必须牢牢把握住这一点。生成器返回去开始运算,<code>n = 10</code>而不是1没问题吧,这个在上面提到的文章中已经提到了,就是add(i+n)绑定的是<code>n</code>这个变量,而不是它当时的数值。然后首先是第一次生成器表达式的执行过程:<code>base = (10 + 0, 10 + 1, 10 + 2, 10 +3)</code>,这是第一次循环的结果(形象表示,其实已经计算出来了(10,11,12,3)),然后第二次,<code>base = (10 + 10, 11 + 10, 12 + 10, 13 + 10)</code> ,终于得到结果了<code>[20, 21, 22, 23]</code>.</p></blockquote>
<p><strong>新思路</strong><br>这个可以以管道的思路来理解,首先<code>gen()</code>函数是第一个生成器,下一个是第一次循环的<code>base = (add(i, n) for i in base)</code>,最后一个生成器是第二次循环的<code>base = (add(i, n) for i in base)</code>。<br>这样就相当于三个管道依次连接,但是水(数据)还没有流过,现在到了<code>list(base)</code>,就相当于驱动器,打开了水的开关,这时候,按照管道的顺序,由第一个产生一个数据,<code>yield 0</code>,然后第一个管道关闭。<br>之后传递给第二个管道就是第一次循环,此时执行了<code>add(0, 10)</code>,然后水继续流,到第二次循环,再执行<code>add(10, 10)</code>,此时到管道尾巴了,此时产生了第一个数据20,然后第一个管道再开放:<code>yield 1</code>, 流程跟上面的一样,依次产生21,22,23; 直到没有数据。<br>把代码改一下容易理解:</p>
<pre><code>def gen():
for i in range(4):
yield i # 第一个管道
base = (add(i, 10) for i in base) # 第二个管道
base = (add(i, 10) for i in base) # 第三个管道
list(base) # 开关驱动器</code></pre>
<p>具体执行过程可以在<a href="https://link.segmentfault.com/?enc=7HREoA1InxL2EEDUhX2OcA%3D%3D.ZSVA%2Fsyp620puHLQtC%2FecbYykCLymynyujacvqK1r%2FLOOxg1y3zwqJMw02As20h1jPhVw1DfVcQlIQp2ipSQvw%3D%3D" rel="nofollow">pythontutor</a>上:<br>之前的解释被误导的原因是,可能会误以为是在第二个管道就把<code>gen()</code>执行完毕了,其实不是这样的。<br>这种写法的好处显而易见:内存占用低。在数据量极大的时候,用<code>list</code>就只能爆内存,而用生成器模式则完全不用担心</p>
<h2>小结</h2>
<h3>概括</h3>
<p>主要介绍了大概这样几点:</p>
<ul>
<li><p><code>iterable</code>,<code>iterator</code>与<code>itertion</code>的概念</p></li>
<li>
<p>迭代器协议</p>
<ul><li><p>自定义可迭代对象与迭代器分离,保证数据复用</p></li></ul>
</li>
<li><p>生成器: 特殊的迭代器,内部实现了迭代器协议</p></li>
</ul>
<p>其实这一块, 那几个概念搞清楚, ,这个很关键, 搞懂了后面就水到渠成了。而且对之前的知识也有很多加深。<br>比如常见<code>list</code>就是<code>iterator</code>与<code>iteable</code>分离实现的,本身是可迭代对象,但不是迭代器, 类似与<code>xrange</code>,但是又不同。<br>越来越明白,看源码的重要性了。</p>
<h3>参考</h3>
<ul>
<li><p><a href="https://link.segmentfault.com/?enc=Zs16jD0NkrDQv3LydeQAAg%3D%3D.2lBHFNMyhghDuSWI0elpaw2swWiegH8CXAe5yhAc7%2FCZhPiDcJ4zfEVan4AzHgr%2FIfEbM06BQKDlEGqieHh2xbk4MFVwd1353aCHf2T0Nv4%3D" rel="nofollow">http://www.shutupandship.com/2012/01/understanding-python-iterables-and.html</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=ZZU1UEyPKGgWrSeDGDHT2g%3D%3D.B%2Fqoy%2FpwdXZK3ZGSOE8K28aGAeoY6CyWarzJIa7U%2BentLMdAkGJjYl8meWsW0UeA1saoSBh7lRwNJ1bSB0b4sGBRWqogMr0o9UkoUeEqBFKFskCT2enNIwFbfQ3iTb9C" rel="nofollow">http://www.learningpython.com/2009/02/23/iterators-iterables-and-generators-oh-my/</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=bsWMZ4SXtcVuZx2cTOGddg%3D%3D.b2Mt%2BxjIVoFLgeg%2F0ktAPZhQHlr5OhFssnaFDjs7olVB18ggJLGuhJEIZ3EK8vI5UvftUe1Bw%2BwCQ6R%2BM7IYat8KP%2BJimoNg1NdiY%2BjsZd0fMMFgBEfWeKV7VPwfiFA1WUY%2BvwaTbuliNGV%2BWlo7WA%3D%3D" rel="nofollow">http://stackoverflow.com/questions/9884132/what-exactly-are-pythons-iterator-iterable-and-iteration-protocols</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=xs2179SdUFZMjhd9gdZmNg%3D%3D.I%2FySlZndVKc8x8qVXCnI61Vk%2FPzza5t8UVDM8UPqT7VkxvNR%2FbinoTOKk8QrBkVy" rel="nofollow">http://python.jobbole.com/81881/</a></p></li>
</ul>
hexo同时托管到coding.net与github
https://segmentfault.com/a/1190000004548638
2016-03-06T12:11:14+08:00
2016-03-06T12:11:14+08:00
shomy
https://segmentfault.com/u/shomy
0
<blockquote><p>2016.03.07更新:<br>部署到coding时,如果使用pages的话,项目名称必须与coding的用户名一致,但是演示并没有这个要求。而且目前为止:演示不能绑定自己的域名,而pages可以,所以说,为了以后方便,项目名称还是与用户名一致的好,不然部署pages会出现没有样式的bug.</p></blockquote>
<blockquote><p>源自:<a href="https://link.segmentfault.com/?enc=1hb7X4Jjqw5Nv2SmR0gboQ%3D%3D.iN38fgL70ZG%2BV%2FuiXnahZZX7valTrvO%2BWNmzusud64U4NnlDDnXxjbFteehkHiW%2BJMsYL6jkHyVGQStu%2FhFn7Q%3D%3D" rel="nofollow">我的博客</a></p></blockquote>
<p>之前已经把<code>hexo</code>部署到<code>github</code>,但是有时候挺慢的,于是就像跟大家一样搞到国内的<code>gitcafe</code>上,岂料,上到<code>gitcafe</code>官网才发现它已经被<code>coding.net</code>收购了,而且<strong>2016.5.31之后停止服务</strong>。<br><img src="http://7xotye.com1.z0.glb.clouddn.com/coding6.png" alt="" title=""></p>
<p>于是直接去了<code>coding</code>上部署了。结果有一点问题,耽误了好长时间。。。记录一下吧。</p>
<h2>coding配置</h2>
<p>基本流程跟<code>github</code>一样,都是先申请帐号。这里需要说一下的是,申请<code>coding</code>的邮箱尽量与<code>github</code>使用同一个邮箱,这样配置<code>ssh</code>就省了事情,不用配置两个ssh了。</p>
<h3>ssh对接</h3>
<p>基本一样,如果之前生成过<code>ssh</code>的话,就可以直接使用,一般存放在<code>~/.ssh/id_rsa.pub</code>。如果没有的话,那就下面几步就可以了</p>
<pre><code>$ ssh-keygen -t rsa -b 4096 -C "你的邮箱" </code></pre>
<p>后面就直接默认,按回车就好。<br>然后就是:<br><!--more--></p>
<pre><code>$ ssh-add</code></pre>
<p>然后在<code>coding</code>的个人页面,如下<br><img src="http://7xotye.com1.z0.glb.clouddn.com/coding1.png" alt="" title=""></p>
<blockquote><p>注意这是在个人账户下添加了<code>ssh</code>,以后新建了项目,项目也有个<code>ssh</code>,但是那个是只读的,不要加到那里去。只需要在个人账户下,加入了即可。</p></blockquote>
<p>加入了之后,执行验证:</p>
<pre><code>ssh -T git@git.coding.net</code></pre>
<p>提示如下即可:</p>
<pre><code>Hello shomyliu You've connected to Coding.net by SSH successfully!</code></pre>
<p>如果失败了,则检查一下是不是在<code>coding</code>那里对了,基本流程跟<code>github</code>一样。这一步ok了。</p>
<h3>新建项目</h3>
<p>这一步也很简单,直接新建一个项目就好了,注意用户名尽量与项目名称保持一致。另外网上有人说得是最好是公有的,所以就搞了一个公有的。其余的都不用动,直接默认就行了。</p>
<h2>hexo配置</h2>
<p>新建好了<code>coding</code>的项目之后,修改一下站点的配置文件,<code>_config.yml</code>如下:</p>
<pre><code>deploy:
type: git
repository:
github: git@github.com:ShomyLiu/ShomyLiu.github.io.git
coding: git@git.coding.net:shomyliu/shomyliu.git
branch: master</code></pre>
<p>就是在原来的基础再加一个就好了。只需要把repo的地址改成自己对应项目的就好。<br>还有一步,就是在<code>source/</code>需要创建一个空白文件,至于原因,是因为 <code>coding.net</code>需要这个文件来作为以静态文件部署的标志。就是说看到这个<code>Staticfile</code>就知道按照静态文件来发布。</p>
<pre><code>cd source/
touch Staticfile #名字必须是Staticfile</code></pre>
<p>现在可以<code>deploy</code>了。</p>
<pre><code>$ hexo clean
$ hexo g
$ hexo d</code></pre>
<p>会看到有两个发布。发布完成之后,还需要托管<code>coding.net</code>上。</p>
<h2>部署</h2>
<p>然后下面开始部署:有两种方式</p>
<h3>使用<code>Pages</code>部署</h3>
<p>这种方式的话,就跟<code>Github Pages</code>一样。只需要在如下:<br><img src="http://7xotye.com1.z0.glb.clouddn.com/coding5.png" alt="" title=""><br>因为前面配置的分支是<code>master</code>,因此开启之后,也需要是<code>master</code>。然后看起之后就可访问了,下面两个链接都可以访问</p>
<pre><code>http://shomyliu.coding.me/shomyliu
http://shomyliu.coding.me/</code></pre>
<h3>使用<code>演示</code>
</h3>
<p>这种方式类似与使用<code>coding.net</code>的主机了。但是貌似每次部署更新不及时,可以尝试。<br>如图,打开自己的新建的项目,在代码区域,可以看到已经<code>push</code>上去了。现在打开<code>演示一栏</code>:<br><img src="http://7xotye.com1.z0.glb.clouddn.com/coding2.png" alt="" title=""><br>直接 <code>开始检测</code>, 一般情况,如果按照上面的步骤来的话,尤其是创建了<code>Staticfile</code>这个空文件的话,这里是可以正常开启的。如果不行的话,就强制开始,然后在进去如下:<br><img src="http://7xotye.com1.z0.glb.clouddn.com/coding3.png" alt="" title=""><br>如果是正常开启的话, 系统会自动检测运行环境,这时候,就没有必要修改高级选项了,如果是强制开启的话,那就需要修改了,指定运行环境是<code>html</code>. 然后运行内存 可以改小一点,毕竟静态博客也就占用几M内存。<br>之后在一键部署,稍等一会(时间有长有短),部署成功如下显示:<br><img src="http://7xotye.com1.z0.glb.clouddn.com/coding4.png" alt="" title=""><br>这时候,访问你的网页就可以了,与<code>github.io</code>一模一样。<br>此时还有一步,就是为了可以每次更新都可以立刻部署到线上,这里就需要配置一下<code>webhook</code>,这里参考了<a href="https://link.segmentfault.com/?enc=4VpsIDV3F1l96kU4G%2Bs%2BgA%3D%3D.bIYc3I6fFKRdYWvJyVv0JuPwAO2fadru6AGhwaLW%2BA2KCTpGNOVJNAxSOk%2BeSClGHA7t68cdbk3D0V5VTsst%2F0LWiKoWfA10Lueb6YLsI%2BIfDLWLlFJqyYQPYZ1imd3Yu3OuGR8aiSp%2FBFfYYVyrJr3oKiCt7joKN5CaLjqWXBJo8ZhP1xusAWA6tduu8MQpdiMSGkLZ7h18Wsvw6LmAu4NA6c7qNuDo2zko3kM8%2BFmy7JXof4pV4H%2FgnaNVtbsRcKETLcH05HdRuIQy7Z9DY6ypBKTww1n0CUww8x7TQgM%3D" rel="nofollow">这篇</a><br>首先在项目设置里面找到<code>webhook</code>:然后新建一个webhook,域名后面记得加上<code>/_</code><br><img src="http://7xotye.com1.z0.glb.clouddn.com/coding9.png" alt="" title=""><br>如图, tooken可以随意写。之后添加了之后,回到演示页面,找到环境变量:如下:添加即可webhook:<br><img src="http://7xotye.com1.z0.glb.clouddn.com/coding10.png" alt="" title=""><br>之后再终止掉演示,重新部署,再次开启虚拟机,就最终完成了.</p>
<p>可以看出第一种方式比较方便,建议使用。</p>
由一个例子到python的名字空间
https://segmentfault.com/a/1190000004523118
2016-03-02T11:57:53+08:00
2016-03-02T11:57:53+08:00
shomy
https://segmentfault.com/u/shomy
4
<blockquote><p>源自<a href="https://link.segmentfault.com/?enc=E4ol35sDt7zn%2F14xjlYsKA%3D%3D.UOVQsCPSEWUMTB2GqA7nA4KWRL8LLAvfUn6FOljFn1plXcuuCc%2BXGU7gaXQqhcQ90cy3E2xCUu0LX0CBWw92Aw%3D%3D" rel="nofollow">我的博客</a></p></blockquote>
<h2>前言</h2>
<blockquote><p><code>python</code>里面最核心的内容就是:名字空间(namespace)</p></blockquote>
<hr>
<h2>例子引入</h2>
<p>例1</p>
<pre><code>#!/usr/bin/env python
# encoding: utf-8
def func1():
x = 1
print globals()
print 'before func1:', locals()
def func2():
a = 1
print 'before fun2:', locals()
a += x
print 'after fun2:', locals()
func2()
print 'after func1:', locals()
print globals()
if __name__ == '__main__':
func1()</code></pre>
<p>可以正常输出结果: 并且需要注意,在<code>func2</code>使用<code>x</code>变量之前的名字空间就已经有了<code>'x':1</code>.</p>
<pre><code>before func1: {'x': 1}
before fun2: {'a': 1, 'x': 1}
after fun2: {'a': 2, 'x': 1}
after func1: {'x': 1, 'func2': <function func2 at 0x7f7c89700b90>}</code></pre>
<p>稍微改一点:如下</p>
<p>例2:</p>
<pre><code>#!/usr/bin/env python
# encoding: utf-8
def func1():
x = 1
print 'before func1:', locals()
def func2():
print 'before fun2:', locals()
x += x #就是这里使用x其余地方不变
print 'after fun2:', locals()
func2()
print 'after func1:', locals()
if __name__ == '__main__':
func1()</code></pre>
<p>输出就开始报错: 而且在<code>before func2</code>也没有了<code>x</code>.</p>
<pre><code>before func1: {'x': 1}
before fun2: {}
Traceback (most recent call last):
File "test.py", line 18, in <module>
func1()
File "test.py", line 14, in func1
func2()
File "test.py", line 11, in func2
x += x
UnboundLocalError: local variable 'x' referenced before assignment
</code></pre>
<p>这两个例子正好涉及到了<code>python</code>里面最核心的内容:<strong>名字空间</strong>,正好总结一下,然后在解释这几个例子。</p>
<hr>
<h2>名字空间(Namespace)</h2>
<p>比如我们定义一个"变量"</p>
<pre><code>In [14]: a
NameError: name 'a' is not defined</code></pre>
<p>所以,这里更准确的叫法应该是<code>名字</code>。 一些语言中比如<code>c,c++,java</code> 变量名是内存地址别名, 而Python 的名字就是一个字符串,它与所指向的目标对象关联构成名字空间里面的一个键值对<code>{name: object}</code>,因此可以这么说,python的<code>名字空间</code>就是一个字典.。</p>
<h3>分类</h3>
<p>python里面有很多名字空间,每个地方都有自己的名字空间,互不干扰,不同空间中的两个相同名字的变量之间没有任何联系一般有4种: <code>LEGB</code>四种</p>
<ul>
<li><p><code>locals</code>: 函数内部的名字空间,一般包括函数的局部变量以及形式参数</p></li>
<li><p><code>enclosiing function</code>: 在嵌套函数中外部函数的名字空间, 对<code>fun2</code>来说, <code>fun1</code>的名字空间就是。</p></li>
<li><p><code>globals</code>: 当前的模块空间,模块就是一些<code>py</code>文件。也就是说,globals()类似全局变量。</p></li>
<li><p><code>__builtins__</code>: 内置模块空间, 也就是内置变量或者内置函数的名字空间。</p></li>
</ul>
<p>当程序引用某个变量的名字时,就会从当前名字空间开始搜索。搜索顺序规则便是: <code>LEGB</code>.</p>
<pre><code>locals -> enclosing function -> globals -> __builtins</code></pre>
<p>一层一层的查找,找到了之后,便停止搜索,如果最后没有找到,则抛出在<code>NameError</code>的异常。这里暂时先不讨论<code>赋值</code>操作。<br>比如例1中的<code>a = x + 1</code> 这行代码,需要引用<code>x</code>, 则按照<code>LEGB</code>的顺序查找,locals()也就是<code>func2</code>的名字空间没有,进而开始<code>E</code>,也就是<code>func1</code>,里面有,找到了,停止搜索,还有后续工作,就是把<code>x</code>也加到自己的名字空间,这也是为什么<code>fun2</code>的名字空间里面也有<code>x</code>的原因。</p>
<h3>访问方式</h3>
<p>其实上面都已经说了,这里暂时简单列一下</p>
<ol>
<li><p>使用<code>locals()</code>访问局部命名空间</p></li>
<li><p>使用<code>globals()</code>访问全局命名空间<br>这里有一点需要注意,就是涉及到了<code>from A import B</code> 和<code>import A</code>的一点区别。</p></li>
</ol>
<pre><code>#!/usr/bin/env python
# encoding: utf-8
import copy
from copy import deepcopy
def func():
x = 123
print 'func locals:',locals()
s = 'hello world'
if __name__ == '__main__':
func()
print 'globals:', globals()</code></pre>
<p>输出结果:</p>
<pre><code>func locals: {'x': 123}
globals: {'__builtins__': <module '__builtin__' (built-in)>,
'__file__': 'test.py',
'__package__': None,
's': 'hello world',
'func': <function func at 0x7f1c3d617c80>,
'deepcopy': <function deepcopy at 0x7f1c3d6177d0>,
'__name__': '__main__',
'copy': <module 'copy' from '/usr/lib/python2.7/copy.pyc'>,
'__doc__': None}</code></pre>
<p>从输出结果可以看出<code>globals()</code>包含了定义的函数,变量等。对于<code> 'deepcopy': <function deepcopy at 0x7f1c3d6177d0></code>可以看出<code>deepcopy</code>已经被导入到自己的名字空间了,而不是在<code>copy</code>里面。 而导入的<code>import copy</code>则还保留着自身的名字空间。因此要访问<code>copy</code>的方法,就需要使用<code>copy.function</code>了。这也就是为什么推荐使用<code>import module</code>的原因,因为<code>from A import B</code>这样会把<code>B</code>引入自身的名字空间,容易发生覆盖或者说污染。</p>
<h3>生存周期</h3>
<p>每个名字空间都有自己的生存周期,如下:</p>
<ul>
<li><p><code>__builtins__</code>: 在<code>python</code>解释器启动的时候,便已经创建,直到退出</p></li>
<li><p><code>globals</code>: 在模块定义被读入时创建,通常也一直保存到解释器退出。</p></li>
<li><p><code>locals</code> : 在<strong>函数调用</strong>时创建, 直到函数返回,或者抛出异常之后,销毁。 另外递归函数每一次均有自己的名字空间。</p></li>
</ul>
<p>看着没有问题,但是有很多地方需要考虑。比如名字空间都是在代码编译时期确定的,而不是执行期间。这个也就可以解释为什么在例1中,<code>before func2:</code>的locals()里面包含了<code>x: 1</code> 这一项。再看下面这个,</p>
<pre><code>def func():
if False:
x = 10 #该语句永远不执行
print x</code></pre>
<p>肯定会报错的,但是错误不是</p>
<pre><code>NameError: global name 'x' is not defined</code></pre>
<p>而是:</p>
<pre><code>UnboundLocalError: local variable 'x' referenced before assignment</code></pre>
<p>虽然<code>x = 10</code>永远不会执行,但是在执行之前的编译阶段,就会把<code>x</code>作为<code>locals</code>变量,但是后面编译到<code>print</code>的时候,发现没有赋值,因此直接抛出异常,<code>locals()</code>里面便不会有<code>x</code>。这个就跟例子2中,<code>before func2</code>里面没有<code>x</code>是一个道理。</p>
<h3>赋值</h3>
<p>为什么要把赋值单独列出来呢,因为赋值操作对名字空间的影响很大,而且很多地方需要注意。<br>核心就是: <strong>赋值修改的是命名空间,而不是对象</strong>, 比如:</p>
<pre><code>a = 10</code></pre>
<p>这个语句就是把<code>a</code>放入到了对应的命名空间, 然后让它指向一个值为10的整数对象。</p>
<pre><code>a = []
a.append(1)
</code></pre>
<p>这个就是把<code>a</code>放入到名字空间,然后指向一个列表对象, 然而后面的<code>a.append(1)</code>这句话只是修改了<code>list</code>的内容,并没有修改它的内存地址。因此<br>并没有涉及到修改名字空间。<br>赋值操作有个特点就是: 赋值操作总是在最里层的作用域.也就说,只要编译到了有赋值操作,就会在当前名字空间内新创建一个名字,然后开始才绑定对象。即便该名字已存在于赋值语句发生的上一层作用域中;</p>
<h2>总结</h2>
<h3>分析例子</h3>
<p>现在再看例子2, 就清晰多了, <code>x += x</code> 编译到这里时,发现了赋值语句,于是准备把<code>x</code>新加入最内层名字空间也就是<code>func2</code>中,即使上层函数已经存在了; 但是赋值的时候,又要用到<code>x</code>的值, 然后就会报错:</p>
<pre><code>UnboundLocalError: local variable 'x' referenced before assignment</code></pre>
<p>这样看起来好像就是 内部函数只可以读取外部函数的变量,而不能做修改,其实本质还是因为<code>赋值</code>涉及到了新建<code>locals()</code>的名字。<br>在稍微改一点:</p>
<pre><code>#!/usr/bin/env python
# encoding: utf-8
def func1():
x = [1,2]
print 'before func1:', locals()
def func2():
print 'before fun2:', locals()
x[0] += x[0] #就是这里使用x[0]其余地方不变
print 'after fun2:', locals()
func2()
print 'after func1:', locals()
if __name__ == '__main__':
func1()
</code></pre>
<p>这个结果就是:</p>
<pre><code>
before func1: {'x': [1, 2]}
before fun2: {'x': [1, 2]}
after fun2: {'x': [2, 2]}
after func1: {'x': [2, 2], 'func2': <function func2 at 0x7fb67b253b18>}
</code></pre>
<p>咋正确了呢---这不应该要报错吗? 其实不然,就跟上面的<code>a.append(1)</code>是一个道理。<br><code>x[0] += x[0]</code> 这个并不是对<code>x</code>的赋值操作。按照<code>LEGB</code>原则, 搜到<code>func1</code>有变量<code>x</code>并且是个<code>list</code>, 然后将其加入到自己的<code>locals()</code>, 后面的<code>x[0] += x[0]</code>, 就开始读取<code>x</code>的元素,并没有影响<code>func2</code>的名字空间。另外无论<code>func1</code>与<code>func2</code>的名字空间的<code>x</code> 没有什么关系,只不过都是对<code>[1, 2]</code>这个列表对象的一个引用。<br>这个例子其实也给了我们一个启发,我们知道内部函数无法直接修改外部函数的变量值,如例2,如果借助<code>list</code>的话, 就可以了吧!比如把想要修改的变量塞到一个<code>list</code>里面,然后在内部函数里面做改变!当然<code>python3.x</code>里面有了<code>nonlocal</code>关键字,直接声明一下就可以修改了。看到这里,对作用域理解应该有一点点了吧。</p>
<h3>延伸</h3>
<h4>与闭包的不同</h4>
<p>我们都知道闭包是把外部函数的值放到<code>func.func_closure</code>里面,为什么不像上面的例子一样直接放到函数的名字空间呢?<br>这是因为<code>locals()</code>空间是在函数调用的时候才创建! 而闭包只是返回了一个函数, 并没有调用,也就没有所谓的空间。</p>
<h4>locals()与globals()</h4>
<p>在最外层的模块空间里<code>locals()</code>就是<code>globals()</code></p>
<pre><code>In [2]: locals() is globals()
Out[2]: True</code></pre>
<p>另外我们可以手动修改<code>globals()</code>来创建名字</p>
<pre><code>In [3]: globals()['a'] = 'abcde'
In [4]: a
Out[4]: 'abcde'</code></pre>
<p>但是<code>locals()</code>在函数里面的话, 貌似是不起作用的,如下:</p>
<pre><code>In [5]: def func():
...: x = 10
...: print x
...: print locals()
...: locals()['x'] = 20
...: print x
In [6]: func()
10
{'x': 10}
10
</code></pre>
<p>这是因为解释器会将 locals 名字复制到 一个叫<code>FAST</code>的 区域来优化访问速度,而实际上解释器访问对象时,是从<code>FAST</code>区域里面读取的,而非<code>locals()</code>。所以直接修改<code>locals()</code>并不能影响<code>x</code>(可以使用<code>exec</code> 动态访问,不再细述)。 不过赋值操作,会同时刷新<code>locals()</code>和<code>FAST</code>区域。</p>
<hr>
<p>查了不少资料,说了这么多,我只想说,作为<code>python</code>最核心的东西,名字空间还有很多很多地方需要探究,比如</p>
<ul>
<li><p>作用域(scope)与名字空间, 这里只是模糊了二者的区别</p></li>
<li><p>面向对象,也就是类的名字空间, 又有不一样的地方。。。</p></li>
</ul>
<p>学一点记录一点吧。</p>
<h2>参考</h2>
<p><a href="https://link.segmentfault.com/?enc=x5LwMl%2BOdlQw2XZlgRwv%2Fw%3D%3D.nUWr4vn0eNYpLek4rlYnrgIRWtMDje0G0I9Ad7PAFduLP4pJXF7gzjaiLvZNEn%2BqYDOmsiiURTJ%2FYZlpZTZEgw%3D%3D" rel="nofollow">1</a><br><a href="https://link.segmentfault.com/?enc=GbFbwKQHWh81iSw1Gfyeag%3D%3D.s9RRSqLCKQfix7pdVoZGpbWVktCZHhduD5Z7W1xKrBmEs7jWEaZWwviQZOQuDbf6aOMcggocsadBKR90AYiKMg%3D%3D" rel="nofollow">2</a><br><a href="https://link.segmentfault.com/?enc=UWZxNEINMTVlNGiu4UAhXg%3D%3D.GNXqhXcLWRSTjxkDjG3G1bc%2F66N44sztecutDey0HjTPdHYwouTK5j%2FSmINFGf9d2M3htWCMinc7brHZSHn%2FIQ%3D%3D" rel="nofollow">3</a></p>
python 默认参数问题及一个应用
https://segmentfault.com/a/1190000004503164
2016-02-28T00:36:12+08:00
2016-02-28T00:36:12+08:00
shomy
https://segmentfault.com/u/shomy
0
<blockquote><p>源自: <a href="https://link.segmentfault.com/?enc=Jp0R3Nywnktlacpe7AaFng%3D%3D.a548mn7lxI9WN8l0DBpzOtiknLnDBEwYKQowr90nUwuBmCU57hhhSAS7YEUBCvCCTjffOCwhfW6hKrdoC4BoKg%3D%3D" rel="nofollow">我的博客</a></p></blockquote>
<p><code>python</code> 里面一个常见的陷阱就是函数的默认参数问题。如下:</p>
<pre><code>
def func(mylist = []):
mylist.append(1)
return mylist
</code></pre>
<p>以下的执行结果如下:</p>
<pre><code>
print func()
print func()
print func()
print func(['a'])
print func()
</code></pre>
<p>结果如下:</p>
<pre><code>
[1]
[1, 1]
[1, 1, 1]
['a', 1]
[1, 1, 1, 1]
</code></pre>
<p>如此结果, 前面三个可以看出 如果没有指定参数的话, 每次调用函数时候, 调用的<code>mylist</code> 是同一个对象。这是因为函数的默认参数,是在代码编译成<code>PyCodeObject</code>的时候, 就已经创建了对象指针,并且存在该函数的<code>func_default</code>内。 以后在代码运行,调用函数的时候,如果没有指定参数的话, 每次调用的话, 该参数变量都是代码编译阶段的变量指针所指定的对象。</p>
<pre><code>
print func.func_default
</code></pre>
<p>此时结果就是:</p>
<pre><code>
([1, 1, 1, 1], )
</code></pre>
<p>默认参数分为两种情况:</p>
<ul>
<li>
<p>默认参数值是不可变对象</p>
<p>此时函数的 <code>func_default</code> 一直指向该不变对象, 如果函数内部修改了该变量, 那么该默认参数会指向一个新的不可变对象. </p>
<p>不过<code>func_default</code> 不变。 而每次调用函数都是读取<code>func_default</code>, 因此每次执行都一样。</p>
<pre><code>
In [30]: def func2(var = 1):
....: var += 1
....: return var
....:
In [31]: func2()
Out[31]: 2
In [32]: func2()
Out[32]: 2
In [34]: func2.func_defaults
Out[34]: (1,)
</code></pre>
</li>
<li>
<p>默认参数是可变对象,比如 <code>list, dict, class</code>等</p>
<p>这种情况下,如果在函数内修改了指针所指的对象(<strong>并未创建新的对象</strong>), 那么 <code>func_default</code> 就会改变。这正是开始的<code>mylist</code>发生变化的原因。看下面的例子,:</p>
<pre><code>
In [35]: def func(mylist = []):
....: mylist = [] #这里 创建了新的对象,
mylist.append(1)
return mylist
In [44]: func()
Out[44]: [1]
In [45]: func.func_defaults
Out[45]: ([],)
由于创建了对象, mylist 只是作为一个 新建对象的别名存在, 后面在修改已经与 func_default 无关了。
</code></pre>
</li>
</ul>
<hr>
<p><strong>默认参数的一个应用</strong></p>
<p>先看下面的一个经典的例子:</p>
<pre><code>
def outer():
res = []
for i in range(4):
def inner(j):
return j * i
res.append(inner)
return res
print [m(2) for m in outer()]
#简略版本:
def multipliers():
return [lambda x : i * x for i in range(4)]
print [m(2) for m in multipliers()]
</code></pre>
<p>结果是 <code>[6, 6, 6, 6]</code> , 而不是 <code>[0, 2, 4, 6]</code>, 原因就是闭包的延迟绑定。另外函数绑定的是变量而不是绑定数值。当循环结束了,<code>i</code>的值已经是<code>3</code>, 此时结果都是<code>6</code>. 一个解决方法便是,使用默认参数绑定数值。如下改动:</p>
<pre><code>
def outer():
res = []
for i in range(4):
def inner(j, i = i):
return j * i
res.append(inner)
return res
print [m(2) for m in outer()]
#简略版本:
def multipliers():
return [lambda x, i = i : i * x for i in range(4)]
print [m(2) for m in multipliers()]
</code></pre>
<p>这样的话, 利用默认参数在代码编译的时候,便把参数写到函数的<code>func_default</code>中, 就可以绑定<code>0,1,2,3</code>了。结果自然就是</p>
<pre><code>
[0, 2, 4, 6]
</code></pre>
<p>这就是默认参数的一个应用。</p>
<blockquote><p>上述还有一个生成器修改的方式</p></blockquote>
<pre><code>
def multipliers():
return (lambda x : i * x for i in range(4)) #修改成生成器
print [m(2) for m in multipliers()]
</code></pre>