Python的冷知识和坑

<h4>声明</h4> <p>此文章为个人翻译,原文出自:<a href="http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/not_so_obvious_python_stuff.ipynb">码农周刊</a>。 <br />除非特殊说明,以下代码皆为<strong>Python3.4</strong>。</p> <h4>译文</h4> <h5>Python使用C3算法解决多重继承问题。</h5> <p>根据C3算法,以下代码在处理多重继承的时候,会在<strong>class B</strong>之前检查<strong>class A</strong>的同名方法<code>foo</code>。 <br />更多详细信息可以查看大神Guido van Rossum的<a href="http://python-history.blogspot.ru/2010/06/method-resolution-order.html">博客</a>。</p> <p><a href="http://gistroll.com/rolls/21/horizontal_assessments/new">In</a>:</p> <pre><code>class A(object): def foo(self): print(&quot;class A&quot;)

class B(object): def foo(self): print("class B")

class C(A, B): pass

C().foo()</code></pre>

<p>Out:</p>

<blockquote> <p>class A</p> </blockquote>

<p>以上例子显示出<strong>class C</strong>首先检查其父类<strong>class A</strong>是否存在方法<code>foo()</code>并尝试调用(因此成功调用了)。</p>

<p>在我收到的一封email中有一个更为巧妙的嵌套例子来阐述Guido van Rossum的观点:</p>

<p>In:</p>

<pre><code>class A(object): def foo(self): print(&quot;class A&quot;) class B(A): pass class C(A): def foo(self): print(&quot;class C&quot;) class D(B,C): pass D().foo()</code></pre>

<p>Out:</p>

<blockquote> <p>class C</p> </blockquote>

<p>这里显示<strong>D</strong>首先搜索<strong>B</strong>,而<strong>B</strong>继承自<strong>A</strong>(注意,<strong>C</strong>同样也继承自<strong>A</strong>,但是<strong>C</strong>重写了方法<code>foo</code>)。因此我们得出的搜索顺序为:<strong>D</strong>, <strong>B</strong>, <strong>C</strong>, <strong>A</strong>。</p>

<h5>列表对于+=运算的表现</h5>

<p>当我们对列表使用<strong>+=</strong>运算符时,我们通过修改对象的方式直接扩展了这个列表。然而,如果我们使用以下方式<code>my_list = my_list + ...</code>,我们则创建了一个新的列表对象,示例代码如下:</p>

<p><a href="http://stackoverflow.com/questions/2347265/why-does-behave-unexpectedly-on-lists">In</a>:</p>

<pre><code>list_a = [] print('ID of list_a', id(list_a)) list_a += [1] print('ID of list_a after `+= [1]`', id(list_a)) list_a = list_a + [2] print('ID of list_a after `list_a = list_a + [2]`', id(list_a))</code></pre>

<p>Out:</p>

<blockquote> <p>ID of list_a 4356439144 <br />ID of list_a after <code>+= [1]</code> 4356439144

<br />ID of list_a after <code>list_a = list_a + [2]</code> 4356446112</p>

</blockquote>

<h5>datetime模块中的True和False的坑</h5>

<p>“它经常会被当成一个大彩蛋被程序员找到(有时候会通过某个难以重现的Bug被找到),不同于其他时间值,凌晨(即<code>datetime.time(0,0,0)</code>)的布尔值是<strong>False</strong>。在python-ideas邮件列表中的一个很长的讨论中显示,虽然很奇葩(原surprising),但是在某些方面这也是可取。”</p>

<p><a href="http://lwn.net/SubscriberLink/590299/bf73fe823974acea/">In</a>:</p>

<pre><code>import datetime print('&quot;datetime.time(0,0,0)&quot; (Midnight) evaluates to', bool(datetime.time(0,0,0))) print('&quot;datetime.time(1,0,0)&quot; (1 am) evaluates to', bool(datetime.time(1,0,0)))</code></pre>

<p>Out:</p>

<blockquote> <p>“datetime.time(0,0,0)” (Midnight) evaluates to False <br />“datetime.time(1,0,0)” (1 am) evaluates to True</p> </blockquote>

<h5>Python会重用一些小整数对象 - 因此经常使用==来判断相等,而用is辨认对象</h5>

<p>因为<strong>Python</strong>很奇葩的保存了一个小整数对象的数组(小整数指介于-5和256之间的数,详见文档)(我收到了一个评论指出这种现象存在于<strong>CPython</strong>,但并不一定存在其他<strong>Python</strong>实现!)</p>

<p>因此使用副标题:因此经常使用<strong>==</strong>来判断相等,而用<strong>is</strong>辨认对象!</p>

<p>这里有个很好的<a href="http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables">文章</a>,解释了这是使用了<strong>boxes</strong>(<strong>C</strong>背景的用法)或<strong>name tags</strong>(<strong>Python</strong>的情况)。</p>

<p>In:</p>

<pre><code>a = 1 b = 1 print('a is b', bool(a is b)) a = 999 b = 999 print('a is b', bool(a is b))</code></pre>

<p>Out:</p>

<blockquote> <p>a is b True <br />a is b False</p> </blockquote>

<p>以下是另一个被经常用到说明小整数对象的重用现象的例子: <br />In:</p>

<pre><code>print(256 is 257 - 1) print(257 is 258 - 1)</code></pre>

<p>Out:</p>

<blockquote> <p>True <br />False</p> </blockquote>

<p>关于<strong>==</strong>和<strong>is</strong>的测试:</p>

<p>In:</p>

<pre><code>a = 'hello world!' b = 'hello world!' print('a is b,', a is b) print('a == b,', a == b)</code></pre>

<p>Out:</p>

<blockquote> <p>a is b, False <br />a == b, True</p> </blockquote>

<p>这个例子说明当==为真时并不能说明两个对象是相等的:</p>

<p>In:</p>

<pre><code>a = float('nan') print('a == a,', a == a) print('a is a,', a is a)</code></pre>

<p>Out:</p>

<blockquote> <p>a == a, False <br />a is a, True</p> </blockquote>

<h5>当一个列表包含其他结构和对象时的浅拷贝和深拷贝</h5>

<p>对包含复合对象的原始列表的修改会影响浅拷贝的对象,但是不会影响深拷贝对象。</p>

<p>In:</p>

<pre><code>from copy import deepcopy my_first_list = [[1],[2]] my_second_list = [[1],[2]] print('my_first_list == my_second_list:', my_first_list == my_second_list) print('my_first_list is my_second_list:', my_first_list is my_second_list) my_third_list = my_first_list print('my_first_list == my_third_list:', my_first_list == my_third_list) print('my_first_list is my_third_list:', my_first_list is my_third_list) my_shallow_copy = my_first_list[:] print('my_first_list == my_shallow_copy:', my_first_list == my_shallow_copy) print('my_first_list is my_shallow_copy:', my_first_list is my_shallow_copy) my_deep_copy = deepcopy(my_first_list) print('my_first_list == my_deep_copy:', my_first_list == my_deep_copy) print('my_first_list is my_deep_copy:', my_first_list is my_deep_copy) print('\nmy_third_list:', my_third_list) print('my_shallow_copy:', my_shallow_copy) print('my_deep_copy:', my_deep_copy)</code></pre>

<p>Out:</p>

<blockquote> <p>my_first_list[0][0] = 2 <br />print(‘after setting “my_first_list[0][0] = 2”’)

<br />print(‘my_third_list:’, my_third_list)

<br />print(‘my_shallow_copy:’, my_shallow_copy)

<br />print(‘my_deep_copy:’, my_deep_copy)

<br />my_first_list == my_second_list: True

<br />my_first_list is my_second_list: False

<br />my_first_list == my_third_list: True

<br />my_first_list is my_third_list: True

<br />my_first_list == my_shallow_copy: True

<br />my_first_list is my_shallow_copy: False

<br />my_first_list == my_deep_copy: True

<br />my_first_list is my_deep_copy: False</p>

<p>my_third_list: [[1], [2]] <br />my_shallow_copy: [[1], [2]]

<br />my_deep_copy: [[1], [2]]

<br />after setting “my_first_list[0][0] = 2”

<br />my_third_list: [[2], [2]]

<br />my_shallow_copy: [[2], [2]]

<br />my_deep_copy: [[1], [2]]</p>

</blockquote>

<h5>and和or表达式中的值</h5>

<p>如果一个<strong>or</strong>表达式中所有值都为真,<strong>Python</strong>会选择第一个值,而<strong>and</strong>表达式则会选择第二个。</p>

<p><strong>译注:不管and或or表达式都遵循短路原则。</strong></p>

<p>一个读者的指出这是等价的:</p>

<pre><code>a or b == a if a else b a and b == b if a else a</code></pre>

<p><a href="http://gistroll.com/rolls/21/horizontal_assessments/new">In</a>:</p>

<pre><code>result = (2 or 3) * (5 and 7) print('2 * 7 =', result)</code></pre>

<p>Out:</p>

<blockquote> <p>2 * 7 = 14</p> </blockquote>

<h5>不要使用可变对象作为函数的默认参数!</h5>

<p>不要用类似字典,列表或集合这样的可变对象作为函数的默认参数!你可能期望会有一个新的列表在每次不带默认形参对应的实参来调用这个函数的时候被创建,但是事与愿违,所有默认参数只会在第一次函数定义的时候被创建,而不是在每次调用的时候,请看下列代码:</p>

<p><a href="http://docs.python-guide.org/en/latest/writing/gotchas/">In</a>:</p>

<pre><code>def append_to_list(value, def_list=[]): def_list.append(value) return def_list my_list = append_to_list(1) print(my_list) my_other_list = append_to_list(2) print(my_other_list)</code></pre>

<p>Out:</p>

<blockquote> <p>[1] <br />[1, 2]</p> </blockquote>

<p>另一个比较好的例子显示默认参数是在函数创建时被创建的:</p>

<p>In:</p>

<pre><code>import time def report_arg(my_default=time.time()): print(my_default) report_arg() time.sleep(5) report_arg()</code></pre>

<p>Out:</p>

<blockquote> <p>1397764090.456688 <br />1397764090.456688</p> </blockquote>

<h5>注意被消耗了的生成器的坑</h5>

<p>注意对生成器使用<strong>in</strong>运算,一旦有一个位置上的元素被消耗了就不会再回来计算了。</p>

<p>In:</p>

<pre><code>gen = (i for i in range(5)) print('2 in gen,', 2 in gen) print('3 in gen,', 3 in gen) print('1 in gen,', 1 in gen) </code></pre>

<p>Out:</p>

<blockquote> <p>2 in gen, True <br />3 in gen, True

<br />1 in gen, False</p>

</blockquote>

<p>我们可以使用一个<strong>list</strong>来规避这个问题:</p>

<p>In:</p>

<pre><code>l = [i for i in range(5)] print('2 in l,', 2 in l) print('3 in l,', 3 in l) print('1 in l,', 1 in l) </code></pre>

<p>Out:</p>

<blockquote> <p>2 in l, True <br />3 in l, True

<br />1 in l, True</p>

</blockquote>

<h5>bool是int的子类</h5>

<p>在<strong>Python</strong>的历史中(特别是<strong>Python 2.2</strong>),真值是通过1和0来实现的(与<strong>C</strong>相似)。在<strong>Python 2.3</strong>版本,为了兼容老式代码,<strong>bool</strong>是以<strong>int</strong>的子类的方式被引入的。</p>

<p><a href="http://www.peterbe.com/plog/bool-is-int">In</a>:</p>

<pre><code>print('isinstance(True, int):', isinstance(True, int)) print('True + True:', True + True) print('3*True:', 3*True) print('3*True - False:', 3*True - False)</code></pre>

<p>Out:</p>

<blockquote> <p>isinstance(True, int): True <br />True + True: 2

<br />3*True: 3

<br />3*True - False: 3</p>

</blockquote>

<h5>关于在闭包和列表推导式的lambda表达式的坑</h5>

<p>还记得“被消耗的生成器”吗?和这个例子有点关系,但是结果还是意想不到的。</p>

<p>下面的这个例子,我们称为列表推导式中的lambda函数,<code>i</code>的值会在每次调用的时候在列表推导式的范围内被解引用。自从列表被创建后,每当我们循环遍历这个列表,<code>i</code>会被设置为最后的一个值4。

<br /><a href="http://openhome.cc/eGossip/Blog/UnderstandingLambdaClosure3.html">In</a>:</p>

<pre><code>my_list = [lambda: i for i in range(5)] for l in my_list: print(l())</code></pre>

<p>Out:</p>

<blockquote> <p>4 <br />4

<br />4

<br />4

<br />4</p>

</blockquote>

<p>然而这种现象不会应用在生成器:</p>

<p>In:</p>

<pre><code>my_gen = (lambda: n for n in range(5)) for l in my_gen: print(l())</code></pre>

<p>Out:</p>

<blockquote> <p>0 <br />1

<br />2

<br />3

<br />4</p>

</blockquote>

<h5>Python的LEGB作用域方案和global和nonlocal关键字</h5>

<p>Python的LEGB作用域方案(Local -&gt; Enclosed -&gt; Global -&gt; Built-in)其实没有什么十分奇特的地方,但是我们可以通过一些例子看出其还是十分有用!</p>

<h6>global vs. local</h6>

<p>根据<strong>LEGB</strong>规则,<strong>Python</strong>会首先从<strong>local scope</strong>搜索一个变量,如果我们在函数的局部作用域中设置变量<code>x = 1</code>,这不会影响到全局的变量<code>x</code>的。</p>

<p>In:</p>

<pre><code>x = 0 def in_func(): x = 1 print('in_func:', x) in_func() print('global:', x)</code></pre>

<p>Out:</p>

<blockquote> <p>in_func: 1 <br />global: 0</p> </blockquote>

<p>如果我们想在函数的局部作用域中修改全局的变量<strong>x</strong>,我们一般使用关键字<strong>global</strong>来将变量导入到局部的作用域:</p>

<p>In:</p>

<pre><code>x = 0 def in_func(): global x x = 1 print('in_func:', x) in_func() print('global:', x)</code></pre>

<p>Out:</p>

<blockquote> <p>in_func: 1 <br />global: 1</p> </blockquote>

<h6>local vs. enclosed</h6>

<p>现在我们再看一下<strong>local</strong>和<strong>enclosed</strong>的情况。这里我们在外部函数设置了变量<code>x = 1</code>,而在封闭的内部函数中设置了<code>x = 2</code>。由于内部函数会优先使用局部作用域,所以并不会影响外部的变量<strong>x</strong>。</p>

<p>In:</p>

<pre><code>def outer(): x = 1 print('outer before:', x) def inner(): x = 2 print(&quot;inner:&quot;, x) inner() print(&quot;outer after:&quot;, x) outer()</code></pre>

<p>Out:</p>

<blockquote> <p>outer before: 1 <br />inner: 2

<br />outer after: 1</p>

</blockquote>

<p>这时候关键字<strong>nonlocal</strong>就能派上用场了,它允许我们在封闭的作用域中修改变量了<strong>x</strong>:</p>

<p>In:</p>

<pre><code>def outer(): x = 1 print('outer before:', x) def inner(): nonlocal x x = 2 print(&quot;inner:&quot;, x) inner() print(&quot;outer after:&quot;, x) outer()</code></pre>

<p>Out:</p>

<blockquote> <p>outer before: 1 <br />inner: 2

<br />outer after: 2</p>

</blockquote>

<p>一个不可变的元组中的可变的元素也不是那么容易被改变 <br />众所周知,<strong>python</strong>中的元组是不可变的对象是吧?但是如果其包含一个可变的对象会怎么发生什么事呢?</p>

<p>首先,我们看一下期望中的表现:如果我们试图修改元组中不可变类型的元素,一个<strong>TypeError</strong>异常会被抛出:</p>

<p>In:</p>

<pre><code>tup = (1,) tup[0] += 1</code></pre>

<p>Out:</p>

<pre><code>--------------------------------------------------------------------------- TypeError Traceback (most recent call last) &lt;ipython-input-41-c3bec6c3fe6f&gt; in &lt;module&gt;() 1 tup = (1,) ----&gt; 2 tup[0] += 1 TypeError: 'tuple' object does not support item assignment</code></pre>

<p>但是我们将一个可变对象放进一个元组中呢?好,当我们修改代码后,我们依然得到了一个<strong>TypeError</strong>异常:</p>

<p>In:</p>

<pre><code>tup = ([],) print('tup before: ', tup) tup[0] += [1] tup before: ([],) print('tup after: ', tup) tup after: ([1],)</code></pre>

<p>Out:</p>

<pre><code>--------------------------------------------------------------------------- TypeError Traceback (most recent call last) &lt;ipython-input-42-aebe9a31dbeb&gt; in &lt;module&gt;() 1 tup = ([],) 2 print('tup before: ', tup) ----&gt; 3 tup[0] += [1] TypeError: 'tuple' object does not support item assignment</code></pre>

<p>但是,依然有很多方法来修改元组中的可变对象而又会抛出<strong>TypeError</strong>异常,例如列表可以使用<code>extend</code>或<code>append</code>方法:</p>

<p>In:</p>

<pre><code>tup = ([],) print('tup before: ', tup) tup[0].extend([1]) print('tup after: ', tup) tup = ([],) print('tup before: ', tup) tup[0].append(1) print('tup after: ', tup)</code></pre>

<p>Out:</p>

<pre><code>tup before: ([],) tup after: ([1],) tup before: ([],) tup after: ([1],)</code></pre>

<h6>解释</h6>

<p>A. Jesse Jiryu Davis对这个现象给出了很好的解释。</p>

<p>如果我们试图使用<strong>+=</strong>去扩展一个列表,那么这条语句执行了字节码<strong>STORE_SUBSCR</strong>,<strong>STORE_SUBSCR</strong>会调用<strong>C</strong>函数<strong>PyObject_SetItem</strong>,这个函数会检查这个对象是否支持项目分配。在我们这里这个对象就是一个元组,因此<strong>PyObject_SetItem</strong>抛出了<strong>TypeError</strong>的异常。所以这个问题被神秘地解决了。</p>

<p>有一个更值得注意的是元组的不可变的状态,元组是出了名的不能改变,然而为什么以下代码却没有问题?</p>

<p><a href="http://emptysqua.re/blog/python-increment-is-weird-part-ii/">In</a>:</p>

<pre><code>my_tup = (1,) my_tup += (4,) my_tup = my_tup + (5,) print(my_tup)</code></pre>

<p>Out:</p>

<pre><code>(1, 4, 5)</code></pre>

<p>事实上,元组并没有被修改,而是每次元组都生成了一个新的对象,而新对象则替换了以前对象的“称呼”:</p>

<p>In:</p>

<pre><code>my_tup = (1,) print(id(my_tup)) my_tup += (4,) print(id(my_tup)) my_tup = my_tup + (5,) print(id(my_tup))</code></pre>

<p>Out:</p>

<pre><code>4337381840 4357415496 4357289952</code></pre>

<h5>生成器比列表推导式更快?</h5>

<p>是的,这是真的(而且还很明显,参见以下的基准例子)。但是在什么时候我们更喜欢其中一个而不是另那个?</p>

<ul> <li>当你想使用列表方法的时候使用列表。 </li>

<li>当希望避免会导致内存问题的大集合的处理时使用生成器。</li> </ul>

<p>In:</p>

<pre><code>import timeit def plainlist(n=100000): my_list = [] for i in range(n): if i % 5 == 0: my_list.append(i) return my_list def listcompr(n=100000): my_list = [i for i in range(n) if i % 5 == 0] return my_list def generator(n=100000): my_gen = (i for i in range(n) if i % 5 == 0) return my_gen def generator_yield(n=100000): for i in range(n): if i % 5 == 0: yield i # To be fair to the list, let us exhaust the generators: def test_plainlist(plain_list): for i in plain_list(): pass def test_listcompr(listcompr): for i in listcompr(): pass def test_generator(generator): for i in generator(): pass def test_generator_yield(generator_yield): for i in generator_yield(): pass print('plain_list: ', end = '') %timeit test_plainlist(plainlist) print('\nlistcompr: ', end = '') %timeit test_listcompr(listcompr) print('\ngenerator: ', end = '') %timeit test_generator(generator) print('\ngenerator_yield: ', end = '') %timeit test_generator_yield(generator_yield)</code></pre>

<p>Out:</p>

<blockquote> <p>plain_list: 10 loops, best of 3: 22.4 ms per loop <br />listcompr: 10 loops, best of 3: 20.8 ms per loop

<br />generator: 10 loops, best of 3: 22 ms per loop

<br />generator_yield: 10 loops, best of 3: 21.9 ms per loop</p>

</blockquote>

<h5>公有方法 vs. 私有方法与名称矫正</h5>

<p>谁还没有在<strong>Python</strong>社区中被这句话绊倒过 - “我们这里都是被承认的成年人”?不同于其他像<strong>C++</strong>的语言(不好意思噜,还有很多呢,可是我对这个最熟哦),我们不能真正地保护类方法在类外被调用。</p>

<p>所有我们能够做的是为一个方法设置一个私用标志来表明他们最好不要在类外部使用,但是这事实上只能由用户来决定,因为“我们这里都是被承认的成年人”!</p>

<p>所以,当我们想设置一个类的私有方法,只需要在方法名前加上两个下划线(其他类成员也一样),使得我们要想从类外访问时必须要进行名称矫正!</p>

<p>这虽然不会阻止类用户去访问类成员,但是他们需要了解这些小把戏并且明白这样做的风险…</p>

<p>让下面这个例子来说明:</p>

<p>In:</p>

<pre><code>class my_class(): def public_method(self): print('Hello public world!') def __private_method(self): print('Hello private world!') def call_private_method_in_class(self): self.__private_method() my_instance = my_class() my_instance.public_method() my_instance._my_class__private_method() my_instance.call_private_method_in_class()</code></pre>

<p>Out:</p>

<pre><code>Hello public world! Hello private world! Hello private world!</code></pre>

<h5>在迭代一个列表的时候修改它</h5>

<p>当迭代的时候去修改它绝对是个很危险的事情 - 这是一个很常见的坑导致一些意外的行为!</p>

<p>看下以下的例子,和一个有趣的练习:在跳到解决方案前尝试解释一下真正发现了什么事!</p>

<p>In:</p>

<pre><code>a = [1, 2, 3, 4, 5] for i in a: if not i % 2: a.remove(i) print(a)</code></pre>

<p>Out:</p>

<blockquote> <p>[1, 3, 5]</p> </blockquote>

<p>In:</p>

<pre><code>b = [2, 4, 5, 6] for i in b: if not i % 2: b.remove(i) print(b)</code></pre>

<p>Out:</p>

<blockquote> <p>[4, 5]</p> </blockquote>

<p>解决方案是我们使用索引来迭代一个列表,如果我们移除其中一项,无可避免的会弄乱索引,请看以下例子,会很清晰地显示出这个问题:</p>

<p>In:</p>

<pre><code>b = [2, 4, 5, 6] for index, item in enumerate(b): print(index, item) if not item % 2: b.remove(item) print(b)</code></pre>

<p>Out:</p>

<blockquote> <p>0 2 <br />1 5

<br />2 6

<br />[4, 5]</p>

</blockquote>

<h5>动态绑定和变量拼写错误</h5>

<p>注意,动态绑定虽然很方便,但是也会很迅速变得尤为危险!</p>

<p>In:</p>

<pre><code>print('first list:') for i in range(3): print(i) print('\nsecond list:') for j in range(3): print(i) # I (intentionally) made typo here!</code></pre>

<p>Out:</p>

<blockquote> <p>first list: <br />0

<br />1

<br />2</p>

<p>second list: <br />2

<br />2

<br />2</p>

</blockquote>

<h5>使用下标进行列表切片导致”out of range”异常</h5>

<p>我们都曾经遭遇过N次声名狼藉的<strong>IndexError</strong>异常:</p>

<p>In:</p>

<pre><code>my_list = [1, 2, 3, 4, 5] print(my_list[5])</code></pre>

<p>Out:</p>

<pre><code>--------------------------------------------------------------------------- IndexError Traceback (most recent call last) &lt;ipython-input-15-eb273dc36fdc&gt; in &lt;module&gt;() 1 my_list = [1, 2, 3, 4, 5] ----&gt; 2 print(my_list[5]) IndexError: list index out of range</code></pre>

<p>但是很神奇的,以下的切片方法却不会抛出异常,在调试的时候这会让你很<strong>O</strong>疼:</p>

<p>In:</p>

<pre><code>my_list = [1, 2, 3, 4, 5] print(my_list[5:])</code></pre>

<p>Out:</p>

<blockquote> <p>[]</p> </blockquote>

<h5>全局变量的重用导致的UnboundLocalErrors</h5>

<p>通常,在一个函数局部作用域中访问一个全局变量是没有问题的:</p>

<p>In:</p>

<pre><code>def my_func(): print(var) var = 'global' my_func()</code></pre>

<p>Out:</p>

<blockquote> <p>global</p> </blockquote>

<p>而且在局部作用域中使用相同的变量名也不会影响对应的全局变量:</p>

<p>In:</p>

<pre><code>def my_func(): var = 'locally changed' var = 'global' my_func() print(var)</code></pre>

<p>Out:</p>

<blockquote> <p>global</p> </blockquote>

<p>但是我们要小心地处理在局部作用域中重用在全局作用域中存在的变量:</p>

<p>In:</p>

<pre><code>def my_func(): print(var) # want to access global variable var = 'locally changed' # but Python thinks we forgot to define the local variable! var = 'global' my_func()</code></pre>

<p>Out:</p>

<pre><code>--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) &lt;ipython-input-40-3afd870b7c35&gt; in &lt;module&gt;() 4 5 var = 'global' ----&gt; 6 my_func() &lt;ipython-input-40-3afd870b7c35&gt; in my_func() 1 def my_func(): ----&gt; 2 print(var) # want to access global variable 3 var = 'locally changed' 4 5 var = 'global' UnboundLocalError: local variable 'var' referenced before assignment</code></pre>

<p>在以下这个例子里,我们使用了关键字<strong>global</strong>!</p>

<p>In:</p>

<pre><code>def my_func(): global var print(var) # 尝试访问全局变量 var = 'locally changed' # 改变全局变量 var = 'global' my_func() print(var)</code></pre>

<p>Out:</p>

<blockquote> <p>global <br />locally changed</p> </blockquote>

<h5>对可变对象创建拷贝</h5>

<p>我们来设想这样的一个场景,如果我们想扩展一个存储着一个子列表的列表,这时候要注意乘法运算符可能会导致的意想不到的结果:</p>

<p>In:</p>

<pre><code>my_list1 = [[1, 2, 3]] * 2 print('initially ---&gt; ', my_list1) # modify the 1st element of the 2nd sublist my_list1[1][0] = 'a' print(&quot;after my_list1[1][0] = 'a' ---&gt; &quot;, my_list1)</code></pre>

<p>Out:</p>

<blockquote> <p>initially —&gt; [[1, 2, 3], [1, 2, 3]] <br />after my_list1<a href="http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/not_so_obvious_python_stuff.ipynb">1</a>[0] = ‘a’ —&gt; [[‘a’, 2, 3], [‘a’, 2, 3]]</p> </blockquote>

<p>在下面这个例子,我们最好创建一个“新”的对象:</p>

<p>In:</p>

<pre><code>my_list2 = [[1, 2, 3] for i in range(2)] print('initially: ---&gt; ', my_list2) # modify the 1st element of the 2nd sublist my_list2[1][0] = 'a' print(&quot;after my_list2[1][0] = 'a': ---&gt; &quot;, my_list2)</code></pre>

<p>Out:</p>

<blockquote> <p>initially: —&gt; [[1, 2, 3], [1, 2, 3]] <br />after my_list2<a href="http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/not_so_obvious_python_stuff.ipynb">1</a>[0] = ‘a’: —&gt; [[1, 2, 3], [‘a’, 2, 3]]</p> </blockquote>

<p>下面是证明:</p>

<p>In:</p>

<pre><code>for a,b in zip(my_list1, my_list2): print('id my_list1: {}, id my_list2: {}'.format(id(a), id(b)))</code></pre>

<p>Out:</p>

<blockquote> <p>id my_list1: 4350764680, id my_list2: 4350766472 <br />id my_list1: 4350764680, id my_list2: 4350766664</p> </blockquote>

<h5>Python 2和3的关键差异</h5>

<p>已经有很多很好的文章总结了<strong>Python</strong>两个版本间的差异,例如:</p>

<p><a href="https://wiki.python.org/moin/Python2orPython3">https://wiki.python.org/moin/Python2orPython3</a>

<br /><a href="https://docs.python.org/3.0/whatsnew/3.0.html">https://docs.python.org/3.0/whatsnew/3.0.html</a>

<br /><a href="http://python3porting.com/differences.html">http://python3porting.com/differences.html</a>

<br /><a href="https://docs.python.org/3/howto/pyporting.html">https://docs.python.org/3/howto/pyporting.html</a></p>

<p>但是这还是一个很值得谈论的话题,尤其是对于<strong>Python</strong>初学者。

<br />(注意:以下代码主要基于<strong>Python 3.4.0</strong>和<strong>Python 2.7.5</strong>的交互式控制台)</p>

<h6>Unicode</h6>

<ul> <li> <p>Python 2:有使用<strong>ASCII </strong>的<code>str</code>类型和独立的<code>unicode</code>类型,但是没有字节类型。</p> </li>

<li> <p>Python 3:而现在,我们终于有了<strong>Unicode(utf-8)</strong>字符串,以及两种字节类型:<code>byte</code>和<code>bytearrays</code>。</p> </li> </ul>

<p>In:</p>

<pre><code>############# # Python 2 ############# &gt;&gt;&gt; type(unicode('is like a python3 str()')) &lt;type 'unicode'&gt; &gt;&gt;&gt; type(b'byte type does not exist') &lt;type 'str'&gt; &gt;&gt;&gt; 'they are really' + b' the same' 'they are really the same' &gt;&gt;&gt; type(bytearray(b'bytearray oddly does exist though')) &lt;type 'bytearray'&gt; ############# # Python 3 ############# &gt;&gt;&gt; print('strings are now utf-8 \u03BCnico\u0394é!') strings are now utf-8 μnicoΔé! &gt;&gt;&gt; type(b' and we have byte types for storing data') &lt;class 'bytes'&gt; &gt;&gt;&gt; type(bytearray(b'but also bytearrays for those who prefer them over strings')) &lt;class 'bytearray'&gt; &gt;&gt;&gt; 'string' + b'bytes for data' Traceback (most recent call last):s File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt; TypeError: Can't convert 'bytes' object to str implicitly</code></pre>

<h6>输出语句</h6>

<p>这是一个很琐碎但也很合理的变化,<strong>Python 3</strong>使用<code>print()</code>函数替代了<strong>Python 2</strong>中的输出语句<code>print</code>。</p>

<p>In:</p>

<pre><code># Python 2 &gt;&gt;&gt; print 'Hello, World!' Hello, World! &gt;&gt;&gt; print('Hello, World!') Hello, World! # Python 3 &gt;&gt;&gt; print('Hello, World!') Hello, World! &gt;&gt;&gt; print 'Hello, World!' File &quot;&lt;stdin&gt;&quot;, line 1 print 'Hello, World!' ^ SyntaxError: invalid syntax</code></pre>

<p>如果我们想将两条<code>print</code>的内容输出在同一行中,<strong>Python 2</strong>中可以使用逗号,而<strong>Python 3</strong>中则可以使用提供参数<code>end=&quot;&quot;</code>的方式:</p>

<p>In:</p>

<pre><code># Python 2 &gt;&gt;&gt; print &quot;line 1&quot;, ; print 'same line' line 1 same line # Python 3 &gt;&gt;&gt; print(&quot;line 1&quot;, end=&quot;&quot;) ; print (&quot; same line&quot;) line 1 same line</code></pre>

<h6>整数除法</h6>

<p>这是一个在代码移植中颇为危险的事情,因为整型除法的不同表现是个经常被忽略的事情。</p>

<p>因此我依然在<strong>Python 3</strong>中使用<code>float(3/2)</code>或者<code>3/2.0</code>来代替<code>3/2</code>来帮助某些人避免一些麻烦。相反的,也可以在<strong>Python 2</strong>中使用<code>from __future__ import division</code>来兼容<strong>Python 3</strong>的整数除法。</p>

<p>In:</p>

<pre><code># Python 2 &gt;&gt;&gt; 3 / 2 1 &gt;&gt;&gt; 3 // 2 1 &gt;&gt;&gt; 3 / 2.0 1.5 &gt;&gt;&gt; 3 // 2.0 1.0 # Python 3 &gt;&gt;&gt; 3 / 2 1.5 &gt;&gt;&gt; 3 // 2 1 &gt;&gt;&gt; 3 / 2.0 1.5 &gt;&gt;&gt; 3 // 2.0 1.0</code></pre>

<h6>xrange()</h6>

<p>在<strong>Python 2.x</strong>中<code>xrange()</code>是一个颇受人欢迎的提供创建一个可迭代对象的功能的函数。表现得有点像一个生成器(惰性求值),但是后者能够无限迭代。<code>xrange()</code>比<code>range()</code>要更为优势的是速度(例如在一个<strong>for</strong>循环中)- 如果你不会对其进行多次迭代,因为每次都会从头开始。</p>

<p>在<strong>Python 3</strong>,<code>xrange</code>更名为<code>range</code>用以取代原先的<code>range</code>。</p>

<p>In:</p>

<pre><code># Python 2 &gt; python -m timeit 'for i in range(1000000):' ' pass' 10 loops, best of 3: 66 msec per loop &gt; python -m timeit 'for i in xrange(1000000):' ' pass' 10 loops, best of 3: 27.8 msec per loop # Python 3 &gt; python3 -m timeit 'for i in range(1000000):' ' pass' 10 loops, best of 3: 51.1 msec per loop &gt; python3 -m timeit 'for i in xrange(1000000):' ' pass' Traceback (most recent call last): File &quot;/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py&quot;, line 292, in main x = t.timeit(number) File &quot;/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py&quot;, line 178, in timeit timing = self.inner(it, self.timer) File &quot;&lt;timeit-src&gt;&quot;, line 6, in inner for i in xrange(1000000): NameError: name 'xrange' is not defined</code></pre>

<h6>抛出异常</h6>

<p>如果在<strong>Python 3</strong>中没有在抛出的异常参数中闭合圆括号,会引发<code>SyntaxError</code>异常,而在<strong>Python 2</strong>却能接受不管是否闭合圆括号的方式。</p>

<p>In:</p>

<pre><code># Python 2 &gt;&gt;&gt; raise IOError, &quot;file error&quot; Traceback (most recent call last): File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt; IOError: file error &gt;&gt;&gt; raise IOError(&quot;file error&quot;) Traceback (most recent call last): File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt; IOError: file error # Python 3 &gt;&gt;&gt; raise IOError, &quot;file error&quot; File &quot;&lt;stdin&gt;&quot;, line 1 raise IOError, &quot;file error&quot; ^ SyntaxError: invalid syntax &gt;&gt;&gt; raise IOError(&quot;file error&quot;) Traceback (most recent call last): File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt; OSError: file error</code></pre>

<h6>异常处理</h6>

<p><strong>Python 3</strong>中的异常处理也有一点细微的变化。我们现在要用关键字<strong>as</strong>来使用了!</p>

<p>In:</p>

<pre><code># Python 2 &gt;&gt;&gt; try: ... blabla ... except NameError, err: ... print err, '--&gt; our error msg' ... name 'blabla' is not defined --&gt; our error msg # Python 3 &gt;&gt;&gt; try: ... blabla ... except NameError as err: ... print(err, '--&gt; our error msg') ... name 'blabla' is not defined --&gt; our error msg</code></pre>

<h6>next函数和next方法</h6>

<p>在<strong>Python 2.7.5</strong>中你两种方法都可以使用,而在<strong>Python 3</strong>却只能使用<code>next</code>函数了!</p>

<p>In:</p>

<pre><code># Python 2 &gt;&gt;&gt; my_generator = (letter for letter in 'abcdefg') &gt;&gt;&gt; my_generator.next() 'a' &gt;&gt;&gt; next(my_generator) 'b' # Python 3 &gt;&gt;&gt; my_generator = (letter for letter in 'abcdefg') &gt;&gt;&gt; next(my_generator) 'a' &gt;&gt;&gt; my_generator.next() Traceback (most recent call last): File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt; AttributeError: 'generator' object has no attribute 'next'</code></pre>

<hr />

<blockquote> <p>@<a href="http://mrlyc.blogspot.com/">LYC</a>

<br />转载请注明<a href="http://mrlyc.blogspot.com/">出处</a>。</p></blockquote>

转载于:https://my.oschina.net/IMLYC/blog/225315

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值