Python虽然没有C/C++的指针和各种数据类型转换,但不代表它没有一片坦途,对于初学者,再感叹Python的简单和强大之时,可能一不小心就掉到陷阱中去了。为了给后来者警示,特总结Python的各种陷阱,以起到“前车之覆,后车之鉴”的效果。
对于Python函数的默认参数,它只在定义之时执行一次。对于不可变数据类型,如string类型等,可能没什么影响。但对于 list, dictionary,大多数类的实例对象等而言,区别就很大了。如下所示:
def func1(a, b=[]):
b.append(a)
print b
func1(1)
func1(2)
func1(3)
class A(object):
def __init__(self):
print("Init A")
def func2(a, b=A()):
print(a)
func2(10)
func2(20)
func2(30)
输出结果:
[1]
[1, 2]
[1, 2, 3]
Init A
10
20
30
Python函数的默认参数表达式,是在函数定义点,在其定义范围内求值的,而不是在函数调用之时求值。代码如下所示:
index = 100
def func(a = index):
print("a = %d"%(a))
index = 200
func()
输出结果:
a = 100
在Python循环体中,如果动态修改循环集合的大小(比如增加或删除列表、字典的元素),会造成不可预料的结果。如下所示
>>> a = [3, 2, 2, 4]
>>> for i in a:
if i % 2 == 0:
a.remove(i)
>>> a
[3, 2]
一般会认为输出结果为 [3],其实在删除第二个元素“2”时,内部迭代器指示到了1位置,删除“2”后,进入下次循环,迭代器增加1,指示到2(该位置的元素此时已经变为4了),导致第2个“2”被跳过了处理(此时“2”的位置为1)。
为了避免上述问题,需要保证在循环体内,不要动态修改循环集合长度,可以做份复制,或者利用列表的切片(slice)做复制,该方法很简洁实用,但仅限于列表的循环。
代码如下所示,从结果看出,已经达到我们预期目的了。
>>> a = [3, 2, 2, 4]
>>> for i in a[:]:
if i % 2 == 0:
a.remove(i)
>>> a
[3]
>>>
我们都知道,Python的string、tuple等类型都是不可变的(immutable)。但此处的不可变,是指它们的元素不会变(即容器里的元素ID是固定的),但元素的值是可变的(前提是该元素类型是可变的)。如下所示:
>>> a = [1, 2]
>>> b = ["Hello", "World"]
>>> id(a)
19999120
>>> id(b)
19998120
>>> T = (a, b)
>>> T
([1, 2], ['Hello', 'World'])
>>> id(T[0])
19999120
>>> id(T[1])
19998120
>>>
>>> a.append(3)
>>> b.append("!!!")
>>> T
([1, 2, 3], ['Hello', 'World', '!!!'])
>>> id(T[0])
19999120
>>> id(T[1])
19998120
>>>
其中 T 是元组,为不可变类型,但其中的元素 a 和 b 均为列表,是可变的。如果我们改变了 a 和 b 的值,相应地, T 所包含的元素的值也改变了。 T 唯一不变的是,它所包含的元素还是原来的那个 a 和 b 元素(它们的ID还是一样的)。
>>> complex("1+3j")
(1+3j)
>>> complex("1 + 3j")
Traceback (most recent call last): File "", line 1, in
complex("1 + 3j")ValueError: complex() arg is a malformed string>>>
>>> (not 1.5) == 1.0
False
>>> not 1.5 == 1.0
True
>>>
>>> s = [[0], {"name": "csdn"}]
>>> s
[[0], {'name': 'csdn'}]
>>>
>>> t = s * 3
>>> t
[[0], {'name': 'csdn'}, [0], {'name': 'csdn'}, [0], {'name': 'csdn'}]
>>>
>>> s[0].append(1)
>>> s
[[0, 1], {'name': 'csdn'}]
>>> t
[[0, 1], {'name': 'csdn'}, [0, 1], {'name': 'csdn'}, [0, 1], {'name': 'csdn'}]
>>>
>>> s[1]["name"] = "google"
>>> s
[[0, 1], {'name': 'google'}]
>>> t
[[0, 1], {'name': 'google'}, [0, 1], {'name': 'google'}, [0, 1], {'name': 'google'}]
>>>
>>> t[0].remove(1)
>>> t
[[0], {'name': 'google'}, [0], {'name': 'google'}, [0], {'name': 'google'}]
>>> s
[[0], {'name': 'google'}]
从上面的例子,我们可以看出,对于列表 t 而言,它只是对 s 做了浅拷贝(shallow copy),t 引用了 s 中嵌套的元素(一个 list 和 一个 dictionary),所以导致对 s 或 t 中元素的修改,都会引起 t 或 s 中相应元素的变化。
f = open("test.txt", "w")
for i in range(1000):
f.write("a" * i)
f.write("\n")
f.flush()
f.close()
import os
f = open("test.txt", "w")
for i in range(1000):
f.write("a" * i)
f.write("\n")
f.flush()
os.fsync(f.fileno())
f.close()
>>> a = 257
>>> b = 257
>>> a is b
False
>>>
>>> a = 256
>>> b = 256
>>> a is b
True
>>>
>>> a = -5
>>> b = -5
>>> a is b
True
>>> a = -6
>>> b = -6
>>> a is b
False
>>>
看看输出结果啊,就知道该陷阱有多坑人啊!至于为何是这样,只能问问Python的创始人Guido了啊。