python(21): 编程易错点 陷阱清单2

    Python虽然没有C/C++的指针和各种数据类型转换,但不代表它没有一片坦途,对于初学者,再感叹Python的简单和强大之时,可能一不小心就掉到陷阱中去了。为了给后来者警示,特总结Python的各种陷阱,以起到“前车之覆,后车之鉴”的效果。

陷阱1:函数的默认参数表达式在定义范围内求值


     Python函数的默认参数表达式,是在函数定义点,在其定义范围内求值的,而不是在函数调用之时求值。代码如下所示:

index = 100
def func(a = index):
    print("a = %d"%(a))
 
index = 200
func()

输出结果:

a = 100

陷阱2:在循环中不要动态修改循环集合大小


    在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]
>>> 

 陷阱3:”不变“(immutable)类型并不是不变的


我们都知道,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还是一样的)。

陷阱4:内嵌函数 complex 字符串构造不允许含空格


complex ( [ real [, imag ] ] ) 函数允许传入形如“real+imagJ”形式的字符串,但不允许“+”号两边含有空格,这个比较坑人啊。如下所示:

>>> 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>>>
陷阱6:not的运算优先级比非布尔操作符(即除and,or以外的运算符)低
熟悉C/C++等语言的读者,都知道表达式 !a == b 等价于 (!a) == b。但对于Python, not a == b 却等价于 not (a == b)。如下所示:

>>> (not 1.5) == 1.0
False
>>> not 1.5 == 1.0
True
>>> 

陷阱5:序列(str、unicode、list、tuple、bytearray、buffer、xrange等)的“乘法复制”是浅拷贝


Python 的序列类型(Sequence Types)支持类似 S = subs * n 的运算操作,最终 S 的结果为 n 个 subs 的连接。特别要注意到是对于被操作数 subs 是可变类型而言(例如 list, dictionary)而言,如果不明白“乘法复制”连接操作是浅拷贝,将会造成不可预知的问题,在复杂的程序中更难以排查。先看示例:

>>> 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 中相应元素的变化。

陷阱6:flush 函数并不一定会立即把内容写入磁盘中


在使用Python操作文件时,为了将内容实时更新到磁盘文件中,需要使用 flush 函数清空缓存数据,强制写入磁盘中,但事实往往与我们的期望相悖。如下代码并不能保证一定会立即写入磁盘中:

f = open("test.txt", "w")
for i in range(1000):
    f.write("a" * i)
    f.write("\n")
    f.flush()
f.close()

要想理解写入磁盘,需要在flush之后,调用 os.fsync( ) 操作。如下所示:

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

该“陷阱”并不是 python 独有,与底层文件系统有关。在Python的帮助文档中,介绍 flush 函数时,也特别提示了该潜在问题。


陷阱7:内部机制的小整数池对象造成对象判断不一致


Python对小整数(-5-256)的对象,会放入一个小整数池对象中,在该范围内的整数,都指向该对象池的对象;超过该范围,即大于256 或小于-5,就各自分配对象了。用语言可能不好描述,还是看下面的例子吧:

>>> 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陷阱汇总_thomashtq的博客-CSDN博客_python 陷阱

你可能感兴趣的:(Python,python)