Home » python » python的坑
python的坑

近期开始整理以前看过的一些非常好的python文章, 这篇文章就会大概讲一讲Python里面的有些小坑.

MRO

当用到多继承的时候, 在父类中去查找继承的方法的顺序叫做 Method Resolution Order. Python里面属性的查找也会是这样.

Python中老式的类(classic classes)的MRO为 depth-first left-to-right, 就是说是从左往右深度遍历. 举个例子:

In [1]: class A:
   ...:     def name(self):
   ...:         print 'class A'
   ...:

In [2]: class B(A):
   ...:     pass
   ...:

In [3]: class C(A):
   ...:     def name(self):
   ...:         print 'class C'
   ...:

In [4]: class D(B, C):
   ...:     pass

D().name()打印了class A, 它的MRO为: D B A C A. 本来我是想让D继承B, C, 如果B里面没有name方法, 那应该是查找平级的C中的name方法. 但是由于是深度遍历, 所以调用的是A的name方法. 虽然这种多继承的情况比较少见. 但是对于新式类(new-style classes)来说, 就特别普遍了(都继承了object).

为了解决这个坑, 新式类在深度遍历的时候, 如果有重复的字段, 除了最后一次出现的字段的, 删掉其他重复的字段.所以在新式类中找出上述例子中D的name方法时:

In [13]: D.__mro__
Out[13]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

更多关于MRO的信息请看这篇文章.

List操作

直接上代码:

In [1]: a = [1, 2]

In [2]: id(a)
Out[2]: 4452270664

In [3]: a += [3]

In [4]: id(a)
Out[4]: 4452270664

In [5]: a = a + [4]  # 只有+的时候id会改变

In [6]: a
Out[6]: [1, 2, 3, 4]

In [7]: id(a)
Out[7]: 4452271096

In [8]: a.append(5)

In [9]: id(a)
Out[9]: 4452271096

In [10]: a.extend([6])

In [11]: id(a)
Out[11]: 4452271096

对列表的 +, +=, append, extend 操作中, 只有 + 操作会改变列表的id.

is 和 ==

总体来说 is 是判断2者身份(id), 而 == 是判断的值.

In [1]: a = 1

In [2]: b = 1

In [3]: a is b
Out[3]: True

In [4]: a == b
Out[4]: True

In [5]: c = 257

In [6]: d = 257

In [7]: c == d
Out[7]: True

In [8]: c is d
Out[8]: False

In [9]: e = -6

In [10]: f = -6

In [11]: e is f
Out[11]: False

In [12]: e == f
Out[12]: True

python缓存了-5 到 256的对象, 所以在这个之间的相同的数, is返回的都会是 True , 但是不在这个返回就是2个对象所以 is 返回的是 False , 但是 == 总是返回 True .

字符串也有缓存, 对只含有 字母 , 数字 , 下划线 组成的字符串来说, 相同的字符串 is== 返回的是 True 如:

In [1]: a = 'hello'

In [2]: b = 'hello'

In [3]: a is b
Out[3]: True

In [4]: a == b
Out[4]: True

如果包含了其他字符, 则 is 返回 False , ==True :

In [1]: a = '!a'

In [2]: b = '!a'

In [3]: a is b
Out[3]: False

In [4]: a = 'hello world'

In [5]: b = 'hello world'

In [6]: a is b
Out[6]: False

但是也有特例:

In [1]: a = float('nan')

In [2]: a is a
Out[2]: True

In [3]: a == a
Out[3]: False

哈哈哈哈. a 啥也不等于, 连他自己都不等于, 是不是很郁闷?

如果要判断是不是 nan:

In [1]: import math

In [2]: a = float('nan')

In [3]: a
Out[3]: nan

In [4]: math.isnan(a)
Out[4]: True

判断是不是无限大:

In [1]: a = float('Inf')

In [2]: a > 100000
Out[2]: True

In [3]: import math

In [4]: math.isinf(a)
Out[4]: True

浅拷贝 深拷贝

先来看下面的例子:

In [1]: a = [1, 2]

In [2]: id(a)
Out[2]: 4415456176

In [3]: b = a

In [4]: id(b)
Out[4]: 4415456176

In [5]: c = a[:]

In [6]: id(c)
Out[6]: 4415465592

In [7]: d = list(a)

In [8]: id(d)
Out[8]: 4415456608

In [9]: import copy

In [10]: e = copy.copy(a)

In [11]: id(e)
Out[11]: 4415435048

直接赋值很好理解, b的id显然应该和a是一致的, 但是通过切片的方式或者工厂函数以及通过 copy 函数创建的c, d和e却和a有着不同的id. 实际上通过 切片 , 工厂函数 或者 copy 函数的方式来拷贝对象, 就是一种 浅拷贝 . 浅拷贝只拷贝父对象.

再来看一个例子:

In [12]: obj = ['name',['age',18]]

In [13]: a=obj[:]

In [14]: b=list(obj)

In [15]: for x in obj,a,b:
   ....:         print id(x)
   ....:
4415437424
4415437712
4415435768

In [16]: a[0] = 'lisi'

In [17]: b[0] = 'zhangsan'

In [18]:

In [18]: print a
['lisi', ['age', 18]]

In [19]: print b
['zhangsan', ['age', 18]]

In [20]:

In [20]: a[1][1] = 25

In [21]:

In [21]: print a
['lisi', ['age', 25]]

In [22]: print b
['zhangsan', ['age', 25]]

在这个例子中, 修改a和b的名字都不会互相影响, 单只是修改了a的年龄, 为什么就改变b中的年龄呢?

实际上, 我们创建的a与b都是从obj对象的浅拷贝, obj中第一个元素是字符串属于不可变类型(immutable), 第二个元素是列表属于可变类型(mutable). 因此我们进行拷贝对象时, 字符串被显示拷贝重新创建了一个字符串, 而列表只是复制引用, 所以改变列表的元素会影响所有引用对象.

跟多关于python mutable vs immutable data type的介绍请看这里

In [1]: obj = ['name',['age',18]]

In [2]: a=obj[:]

In [3]: b=list(obj)

In [4]: for x in obj,a,b:
   ...:     print id(x[0]),id(x[1])
   ...:
4540997232 4550297144
4540997232 4550297144
4540997232 4550297144

In [5]: a[0] = 'lisi'

In [6]: b[0] = 'zhangsan'

In [7]: for x in obj,a,b:
   ...:     print id(x[0]),id(x[1])
...:
4540997232 4550297144
4550273760 4550297144
4550274624 4550297144

In [8]: a[1][1] = 23

In [9]: b[1][1] = 25

In [10]: for x in obj,a,b:
   ....:     print id(x[0]),id(x[1])
....:
4540997232 4550297144
4550273760 4550297144
4550274624 4550297144

In [11]: obj
Out[11]: ['name', ['age', 25]]

修改a b的name字段时, 由于字符串是不可改变类型的数据, 所以改变它就相当于新创建一个对象. 它的id自然会不一样, 也就不会互相影响, 但是由于list是可变类型的, 修改list里面任何字段都不会影响它的指针地址, 所以id没有变化, 也就会互相影响了. 包括原来的obj对象的年龄都变了.

那我就想复制一个对象和原来的完全没有任何影响应该怎么做呢?

这个时候深拷贝就登场了. 之前例子中出现过 copy 这个模块, 这个模块中除了出现过了 copy 方法来做浅拷贝之外, 还有一个方法 deepcopy , 这个方法就可以对一个对象做深拷贝.

In [12]: import copy

In [13]: obj = ['name',['age',18]]

In [14]: a = copy.deepcopy(obj)

In [15]: b = copy.deepcopy(obj)

In [16]: a[1][1] = 23

In [17]: b[1][1] = 25

In [18]: a
Out[18]: ['name', ['age', 23]]

In [19]: b
Out[19]: ['name', ['age', 25]]

In [20]: obj
Out[20]: ['name', ['age', 18]]

修改元组吧

前面提到了可变类型和不可变类型, 现在就来利用可变类型来修改元组.

In [1]: t = ([], )

In [2]: t[0] += [1]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-91852f1a971a> in <module>()
----> 1 t[0] += [1]

TypeError: 'tuple' object does not support item assignment

In [3]: t
Out[3]: ([1],)

In [4]: t = ([], )

In [5]: t[0].extend([1])

In [6]: t
Out[6]: ([1],)

See? 虽然它抛了异常, 但是还是修改成功了.

+= 到底在干嘛? 看这里

可变参数作为函数参数

这个也是可变参数的坑, 直接上代码:

In [1]: def foo(a=[]):
   ...:     a.append('yo')
   ...:     print a
   ...:

In [2]: foo()
['yo']

In [3]: foo()
['yo', 'yo']

In [4]: foo()
['yo', 'yo', 'yo']

所以不要直接在函数参数上给可变类型赋默认值:

In [1]: def foo(a=None):
   ...:     if not a:
   ...:         a = []
   ...:     a.append('yo')
   ...:     print a
   ...:

In [2]: foo()
['yo']

In [3]: foo()
['yo']

循环中修改列表的索引值

先看一个例子:

In [1]: a = [2, 4, 5, 6]

In [2]: for i in a:
   ...:     if not i % 2:
   ...:         a.remove(i)
   ...:

In [3]: a
Out[3]: [4, 5]

这个结果显然错了, 我们希望得到的是奇数. 这个原因就是在遍历列表的时候修改了它的索引.

再看下面这个例子:

In [1]: a = [2, 4, 5, 6]

In [2]: for count, i in enumerate(a):
   ...:     print count, i
   ...:     if not i % 2:
   ...:         a.remove(i)
   ...:
0 2
1 5
2 6

第一个2被删除没有问题, 由于第一个2被删了, 5的索引值变成了1, 所以4就被跳过了, 导致最终结果错误.

最终希望看更多python高级编程技(大)巧(坑)的请戳这里