2014年4月24日星期四

利用结巴分词和余弦相似性定理对文本进行对比

简介

结巴分词

在自然语言处理中,分词是一个极为重要的基础性工作,结巴分词是中文分词的一种实现。
项目地址:Github
快速安装方法:easy_install jieba

余弦相似性定理

根据Cosine similarity - wikipedia可知,对于两个N维向量,我们可以通过求其夹角来判断其相似程度,公式如下:

similarity=cos(θ)=ABAB=i=1nAi×Bii=1n(Ai)2×i=1n(Bi)2

对于两个句子,我们可以根据句子里的词语成分进行统计,将得到的词频看作这个句子所代表的向量,因此就可以应用余弦相似性定理对两个句子进行比较。
我们知道,两个向量夹角越趋于0则越相似,反之,夹角越趋于90则说明方向越不相交,越不相似。

程序

待比较内容

我使用以下两段文字作为比较内容:

1.txt

天蝎座喜欢折磨人,一会儿对你好,一会儿对你坏,天蝎座很坏,很坏,很坏
天蝎座的女孩坚强果敢淡然,
她的心脏时常会痛
就象那些碾转在自尊下的爱
疼了痛了哪怕心都要死了
也说不出口
她独立,也坚强,
她宁愿忍受太多的寂寞和痛苦也不愿意跟任何人提起,
但其实骨子里是很小女人的,
第一次见到天蝎座女生的时候觉得这女生有点冷,她实不擅长对初识的人崭露过多的笑颜,特别是异性。
一定要对方先流露出对她有好感,她才散发她的热情。
在她的心里有一道栅栏
那就是自尊
她看得比生命更尊贵的自尊。

2.txt

双子座喜欢折磨人,一会儿对你好,一会儿对你坏
双子座的男人果敢淡然
他的心脏时常会痛
就象那些碾转在自尊下的爱,
疼了痛了哪怕心都要死了,
也说不出口
他独立,也坚强
他宁愿忍受太多的寂寞和痛苦也不愿意跟任何人提起
但其实骨子里是很需要人关心的,渴望有一个避风港湾,让他去依靠
他的爱很沉默
那并非是因为他缺少那份勇气
在他的心里有一道栅栏
那就是自尊
他看得比生命更尊贵的自尊

比较结果

Base: 1.txt
Scaning: 2.txt
========== out ==========
Line: 1 Result: 76.35%
Base Line: 天蝎座/ 喜欢/ 折磨/ 人/ ,/ 一会儿/ 对/ 你好/ ,/ 一会儿/ 对/ 你/ 坏/ ,/ 天蝎座/ 很坏/ ,/ 很坏/ ,/ 很坏
Target Line: 双子座/ 喜欢/ 折磨/ 人/ ,/ 一会儿/ 对/ 你好/ ,/ 一会儿/ 对/ 你/ 坏

Line: 2 Result: 50.71%
Base Line: 天蝎座/ 的/ 女孩/ 坚强/ 果敢/ 淡然/ ,
Target Line: 双子座/ 的/ 男人/ 果敢/ 淡然

Line: 3 Result: 80.00%
Base Line: 她/ 的/ 心脏/ 时常/ 会痛
Target Line: 他/ 的/ 心脏/ 时常/ 会痛

Line: 4 Result: 94.28%
Base Line: 就象/ 那些/ 碾转/ 在/ 自尊/ 下/ 的/ 爱
Target Line: 就象/ 那些/ 碾转/ 在/ 自尊/ 下/ 的/ 爱/ ,

Line: 5 Result: 96.82%
Base Line: 疼/ 了/ 痛/ 了/ 哪怕/ 心/ 都/ 要死/ 了
Target Line: 疼/ 了/ 痛/ 了/ 哪怕/ 心/ 都/ 要死/ 了/ ,

Line: 6 Result: 100.00%
Base Line: 也/ 说不出口
Target Line: 也/ 说不出口

Line: 7 Result: 79.06%
Base Line: 她/ 独立/ ,/ 也/ 坚强/ ,
Target Line: 他/ 独立/ ,/ 也/ 坚强

Line: 8 Result: 89.71%
Base Line: 她/ 宁愿/ 忍受/ 太多/ 的/ 寂寞/ 和/ 痛苦/ 也/ 不/ 愿意/ 跟/ 任何人/ 提起/ ,
Target Line: 他/ 宁愿/ 忍受/ 太多/ 的/ 寂寞/ 和/ 痛苦/ 也/ 不/ 愿意/ 跟/ 任何人/ 提起

Line: 9 Result: 52.76%
Base Line: 但/ 其实/ 骨子里/ 是/ 很小/ 女人/ 的/ ,
Target Line: 但/ 其实/ 骨子里/ 是/ 很/ 需要/ 人/ 关心/ 的/ ,/ 渴望/ 有/ 一个/ 避风港/ 湾/ ,/ 让/ 他/ 去/ 依靠

Line: 10 Result: 21.48%
Base Line: 第一次/ 见到/ 天蝎座/ 女生/ 的/ 时候/ 觉得/ 这/ 女生/ 有点/ 冷/ ,/ 她/ 实/ 不/ 擅长/ 对/ 初识/ 的/ 人/ 崭露/ 过多/ 的/ 笑颜/ ,/ 特别/ 是/ 异性/ 。
Target Line: 他/ 的/ 爱/ 很/ 沉默

Line: 11 Result: 0.00%
Base Line: 一定/ 要/ 对方/ 先/ 流露出/ 对/ 她/ 有/ 好感/ ,/ 她/ 才/ 散发/ 她/ 的/ 热情/ 。
Target Line: 那/ 并非/ 是因为/ 他/ 缺少/ 那/ 份/ 勇气

Line: 12 Result: 85.71%
Base Line: 在/ 她/ 的/ 心里/ 有/ 一道/ 栅栏
Target Line: 在/ 他/ 的/ 心里/ 有/ 一道/ 栅栏

Line: 13 Result: 100.00%
Base Line: 那/ 就是/ 自尊
Target Line: 那/ 就是/ 自尊

Line: 14 Result: 84.33%
Base Line: 她/ 看/ 得/ 比/ 生命/ 更/ 尊贵/ 的/ 自尊/ 。
Target Line: 他/ 看/ 得/ 比/ 生命/ 更/ 尊贵/ 的/ 自尊

========== End ==========

源程序

源程序地址:Mr.LYC - Git@OSC


@LYC
转载请注明出处

2014年4月21日星期一

Python的冷知识和坑

声明

此文章为个人翻译,原文出自:码农周刊
除非特殊说明,以下代码皆为Python3.4

译文

Python使用C3算法解决多重继承问题。

根据C3算法,以下代码在处理多重继承的时候,会在class B之前检查class A的同名方法foo
更多详细信息可以查看大神Guido van Rossum的博客

In

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

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

class C(A, B):
    pass

C().foo()

Out:

class A

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

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

In:

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

class B(A):
   pass

class C(A):
   def foo(self):
      print("class C")

class D(B,C):
   pass

D().foo()

Out:

class C

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

列表对于+=运算的表现

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

In:

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))

Out:

ID of list_a 4356439144
ID of list_a after += [1] 4356439144
ID of list_a after list_a = list_a + [2] 4356446112

datetime模块中的True和False的坑

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

In:

import datetime

print('"datetime.time(0,0,0)" (Midnight) evaluates to', bool(datetime.time(0,0,0)))

print('"datetime.time(1,0,0)" (1 am) evaluates to', bool(datetime.time(1,0,0)))

Out:

“datetime.time(0,0,0)” (Midnight) evaluates to False
“datetime.time(1,0,0)” (1 am) evaluates to True

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

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

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

这里有个很好的文章,解释了这是使用了boxesC背景的用法)或name tagsPython的情况)。

In:

a = 1
b = 1
print('a is b', bool(a is b))

a = 999
b = 999
print('a is b', bool(a is b))

Out:

a is b True
a is b False

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

print(256 is 257 - 1)
print(257 is 258 - 1)

Out:

True
False

关于==is的测试:

In:

a = 'hello world!'
b = 'hello world!'
print('a is b,', a is b)
print('a == b,', a == b)

Out:

a is b, False
a == b, True

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

In:

a = float('nan')
print('a == a,', a == a)
print('a is a,', a is a)

Out:

a == a, False
a is a, True

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

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

In:

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)

Out:

my_first_list[0][0] = 2
print(‘after setting “my_first_list[0][0] = 2”’)
print(‘my_third_list:’, my_third_list)
print(‘my_shallow_copy:’, my_shallow_copy)
print(‘my_deep_copy:’, my_deep_copy)
my_first_list == my_second_list: True
my_first_list is my_second_list: False
my_first_list == my_third_list: True
my_first_list is my_third_list: True
my_first_list == my_shallow_copy: True
my_first_list is my_shallow_copy: False
my_first_list == my_deep_copy: True
my_first_list is my_deep_copy: False

my_third_list: [[1], [2]]
my_shallow_copy: [[1], [2]]
my_deep_copy: [[1], [2]]
after setting “my_first_list[0][0] = 2”
my_third_list: [[2], [2]]
my_shallow_copy: [[2], [2]]
my_deep_copy: [[1], [2]]

and和or表达式中的值

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

译注:不管and或or表达式都遵循短路原则。

一个读者的指出这是等价的:

a or b == a if a else b
a and b == b if a else a

In

result = (2 or 3) * (5 and 7)
print('2 * 7 =', result)

Out:

2 * 7 = 14

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

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

In

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)

Out:

[1]
[1, 2]

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

In:

import time
def report_arg(my_default=time.time()):
    print(my_default)

report_arg()

time.sleep(5)

report_arg()

Out:

1397764090.456688
1397764090.456688

注意被消耗了的生成器的坑

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

In:

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) 

Out:

2 in gen, True
3 in gen, True
1 in gen, False

我们可以使用一个list来规避这个问题:

In:

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) 

Out:

2 in l, True
3 in l, True
1 in l, True

bool是int的子类

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

In

print('isinstance(True, int):', isinstance(True, int))
print('True + True:', True + True)
print('3*True:', 3*True)
print('3*True - False:', 3*True - False)

Out:

isinstance(True, int): True
True + True: 2
3*True: 3
3*True - False: 3

关于在闭包和列表推导式的lambda表达式的坑

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

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

my_list = [lambda: i for i in range(5)]
for l in my_list:
    print(l())

Out:

4
4
4
4
4

然而这种现象不会应用在生成器:

In:

my_gen = (lambda: n for n in range(5))
for l in my_gen:
    print(l())

Out:

0
1
2
3
4

Python的LEGB作用域方案和global和nonlocal关键字

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

global vs. local

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

In:

x = 0
def in_func():
    x = 1
    print('in_func:', x)

in_func()
print('global:', x)

Out:

in_func: 1
global: 0

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

In:

x = 0
def in_func():
    global x
    x = 1
    print('in_func:', x)

in_func()
print('global:', x)

Out:

in_func: 1
global: 1

local vs. enclosed

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

In:

def outer():
       x = 1
       print('outer before:', x)
       def inner():
           x = 2
           print("inner:", x)
       inner()
       print("outer after:", x)
outer()

Out:

outer before: 1
inner: 2
outer after: 1

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

In:

def outer():
       x = 1
       print('outer before:', x)
       def inner():
           nonlocal x
           x = 2
           print("inner:", x)
       inner()
       print("outer after:", x)
outer()

Out:

outer before: 1
inner: 2
outer after: 2

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

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

In:

tup = (1,)
tup[0] += 1

Out:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-41-c3bec6c3fe6f> in <module>()
      1 tup = (1,)
----> 2 tup[0] += 1

TypeError: 'tuple' object does not support item assignment

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

In:

tup = ([],)
print('tup before: ', tup)
tup[0] += [1]
tup before:  ([],)
print('tup after: ', tup)
tup after:  ([1],)

Out:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-42-aebe9a31dbeb> in <module>()
      1 tup = ([],)
      2 print('tup before: ', tup)
----> 3 tup[0] += [1]

TypeError: 'tuple' object does not support item assignment

但是,依然有很多方法来修改元组中的可变对象而又会抛出TypeError异常,例如列表可以使用extendappend方法:

In:

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)

Out:

tup before:  ([],)
tup after:  ([1],)

tup before:  ([],)
tup after:  ([1],)

解释

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

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

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

In

my_tup = (1,)
my_tup += (4,)
my_tup = my_tup + (5,)
print(my_tup)

Out:

(1, 4, 5)

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

In:

my_tup = (1,)
print(id(my_tup))
my_tup += (4,)
print(id(my_tup))
my_tup = my_tup + (5,)
print(id(my_tup))

Out:

4337381840
4357415496
4357289952

生成器比列表推导式更快?

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

  • 当你想使用列表方法的时候使用列表。
  • 当希望避免会导致内存问题的大集合的处理时使用生成器。

In:

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)

Out:

plain_list: 10 loops, best of 3: 22.4 ms per loop
listcompr: 10 loops, best of 3: 20.8 ms per loop
generator: 10 loops, best of 3: 22 ms per loop
generator_yield: 10 loops, best of 3: 21.9 ms per loop

公有方法 vs. 私有方法与名称矫正

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

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

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

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

让下面这个例子来说明:

In:

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()

Out:

Hello public world!
Hello private world!
Hello private world!

在迭代一个列表的时候修改它

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

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

In:

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

Out:

[1, 3, 5]

In:

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

Out:

[4, 5]

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

In:

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

Out:

0 2
1 5
2 6
[4, 5]

动态绑定和变量拼写错误

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

In:

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!

Out:

first list:
0
1
2

second list:
2
2
2

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

我们都曾经遭遇过N次声名狼藉的IndexError异常:

In:

my_list = [1, 2, 3, 4, 5]
print(my_list[5])

Out:

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-15-eb273dc36fdc> in <module>()
      1 my_list = [1, 2, 3, 4, 5]
----> 2 print(my_list[5])

IndexError: list index out of range

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

In:

my_list = [1, 2, 3, 4, 5]
print(my_list[5:])

Out:

[]

全局变量的重用导致的UnboundLocalErrors

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

In:

def my_func():
    print(var)

var = 'global'
my_func()

Out:

global

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

In:

def my_func():
    var = 'locally changed'

var = 'global'
my_func()
print(var)

Out:

global

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

In:

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()

Out:

---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-40-3afd870b7c35> in <module>()
      4 
      5 var = 'global'
----> 6 my_func()

<ipython-input-40-3afd870b7c35> in my_func()
      1 def my_func():
----> 2     print(var) # want to access global variable
      3     var = 'locally changed'
      4 
      5 var = 'global'

UnboundLocalError: local variable 'var' referenced before assignment

在以下这个例子里,我们使用了关键字global

In:

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

var = 'global'

my_func()
print(var)

Out:

global
locally changed

对可变对象创建拷贝

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

In:

my_list1 = [[1, 2, 3]] * 2

print('initially ---> ', my_list1)

# modify the 1st element of the 2nd sublist
my_list1[1][0] = 'a'
print("after my_list1[1][0] = 'a' ---> ", my_list1)

Out:

initially —> [[1, 2, 3], [1, 2, 3]]
after my_list11[0] = ‘a’ —> [[‘a’, 2, 3], [‘a’, 2, 3]]

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

In:

my_list2 = [[1, 2, 3] for i in range(2)]

print('initially:  ---> ', my_list2)

# modify the 1st element of the 2nd sublist
my_list2[1][0] = 'a'
print("after my_list2[1][0] = 'a':  ---> ", my_list2)

Out:

initially: —> [[1, 2, 3], [1, 2, 3]]
after my_list21[0] = ‘a’: —> [[1, 2, 3], [‘a’, 2, 3]]

下面是证明:

In:

for a,b in zip(my_list1, my_list2):
    print('id my_list1: {}, id my_list2: {}'.format(id(a), id(b)))

Out:

id my_list1: 4350764680, id my_list2: 4350766472
id my_list1: 4350764680, id my_list2: 4350766664

Python 2和3的关键差异

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

https://wiki.python.org/moin/Python2orPython3
https://docs.python.org/3.0/whatsnew/3.0.html
http://python3porting.com/differences.html
https://docs.python.org/3/howto/pyporting.html

但是这还是一个很值得谈论的话题,尤其是对于Python初学者。
(注意:以下代码主要基于Python 3.4.0Python 2.7.5的交互式控制台)

Unicode

  • Python 2:有使用ASCII str类型和独立的unicode类型,但是没有字节类型。

  • Python 3:而现在,我们终于有了Unicode(utf-8)字符串,以及两种字节类型:bytebytearrays

In:

#############
# Python 2
#############

>>> type(unicode('is like a python3 str()'))
<type 'unicode'>

>>> type(b'byte type does not exist')
<type 'str'>

>>> 'they are really' + b' the same'
'they are really the same'

>>> type(bytearray(b'bytearray oddly does exist though'))
<type 'bytearray'>

#############
# Python 3
#############

>>> print('strings are now utf-8 \u03BCnico\u0394é!')
strings are now utf-8 μnicoΔé!


>>> type(b' and we have byte types for storing data')
<class 'bytes'>

>>> type(bytearray(b'but also bytearrays for those who prefer them over strings'))
<class 'bytearray'>

>>> 'string' + b'bytes for data'
Traceback (most recent call last):s
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly

输出语句

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

In:

# Python 2
>>> print 'Hello, World!'
Hello, World!
>>> print('Hello, World!')
Hello, World!

# Python 3
>>> print('Hello, World!')
Hello, World!
>>> print 'Hello, World!'
  File "<stdin>", line 1
    print 'Hello, World!'
                        ^
SyntaxError: invalid syntax

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

In:

# Python 2
>>> print "line 1", ; print 'same line'
line 1 same line

# Python 3
>>> print("line 1", end="") ; print (" same line")
line 1 same line

整数除法

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

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

In:

# Python 2
>>> 3 / 2
1
>>> 3 // 2
1
>>> 3 / 2.0
1.5
>>> 3 // 2.0
1.0

# Python 3
>>> 3 / 2
1.5
>>> 3 // 2
1
>>> 3 / 2.0
1.5
>>> 3 // 2.0
1.0

xrange()

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

Python 3xrange更名为range用以取代原先的range

In:

# Python 2
> python -m timeit 'for i in range(1000000):' ' pass'
10 loops, best of 3: 66 msec per loop

    > python -m timeit 'for i in xrange(1000000):' ' pass'
10 loops, best of 3: 27.8 msec per loop

# Python 3
> python3 -m timeit 'for i in range(1000000):' ' pass'
10 loops, best of 3: 51.1 msec per loop

> python3 -m timeit 'for i in xrange(1000000):' ' pass'
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py", line 292, in main
    x = t.timeit(number)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
    for i in xrange(1000000):
NameError: name 'xrange' is not defined

抛出异常

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

In:

# Python 2
>>> raise IOError, "file error"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: file error
>>> raise IOError("file error")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: file error


# Python 3    
>>> raise IOError, "file error"
  File "<stdin>", line 1
    raise IOError, "file error"
                 ^
SyntaxError: invalid syntax
>>> raise IOError("file error")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: file error

异常处理

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

In:

# Python 2
>>> try:
...     blabla
... except NameError, err:
...     print err, '--> our error msg'
... 
name 'blabla' is not defined --> our error msg

# Python 3
>>> try:
...     blabla
... except NameError as err:
...     print(err, '--> our error msg')
... 
name 'blabla' is not defined --> our error msg

next函数和next方法

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

In:

# Python 2
>>> my_generator = (letter for letter in 'abcdefg')
>>> my_generator.next()
'a'
>>> next(my_generator)
'b'

# Python 3
>>> my_generator = (letter for letter in 'abcdefg')
>>> next(my_generator)
'a'
>>> my_generator.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'generator' object has no attribute 'next'

@LYC
转载请注明出处