python是一门清晰简洁的语言,如果你对一些细节不了解的话,就会掉入到那些深不见底的“坑”里,下面,我就来总结一些python里常见的坑。
列表创建和引用
嵌套列表的创建
使用*号来创建一个嵌套的list:
1
2
3
|
li = [[]] * 3 print (li) # out: [[], [], []] |
通过这个方法,可以得到一个包含3个list的嵌套list,我们来给第一个list增加一个元素:
1
2
3
|
li[ 0 ].append( 1 ) print (li) # out: [[1], [1], [1]] |
通过输出的结果可以看初,我们只给第一元素增加元素,结果三个list都增加了一个元素。这是因为[[]]*3并不是创建了三个不同list,而是创建了三个指向同一个list的对象,所以,当我们操作第一个元素时,其他两个元素内容也会发生变化的原因。效果等同于下面这段代码:
1
2
3
4
5
6
7
8
|
li = [] element = [[]] li = element + element + element print (li) # out: [[], [], []] element.append( 1 ) print (li) # out: [[1], [1], [1]] |
我们可以打印出元素的内存地址一探究竟:
1
2
3
|
li = [[]] * 3 print ([ id (inner_list) for inner_list in li]) # out: [6830760, 6830760, 6830760] |
到这我们可以明白原因了。那如何解决了?可以这样:
1
|
li = [[] for _ in range ( 3 )] |
这样我们就创建了三个不同的list对象
1
2
|
print ([ id (inner_list) for inner_list in li]) # out: [6331048, 6331528, 6331488] |
列表元素的引用
不要使用索引方法遍历list,例如:
1
2
|
for i in range ( len (tab)): print (tab[i]) |
比较好的方法是:
1
2
|
for elem in tab: print (elem) |
for语句会自动生成一个迭代器。如果你需要索引位置和元素,使用enumerate函数:
1
2
|
for i, elem in enumerate (tab): print ((i, elem)) |
注意 == 符号的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
if (var = = true): # 当var是:true、1、 1.0、 1l时if条件成立 if (var ! = true): # 当var不是 true 和 1 时if条件成立 if (var = = false): # 当var是 false 或者 0 (or 0.0, 0l, 0j) if条件成立 if (var = = none): # var是none if条件成立 if var: # 当var非空(none或者大小为0)对象 string/list/dictionary/tuple, non-0等if条件成立 if not var: # 当var空(none或者大小为0)对象 string/list/dictionary/tuple, non-0等if条件成立 if var is true: # 只有当var时true时 if条件成立 1也不行 if var is false: # 只有当var时false时 if条件成立 0也不行 if var is none: # 和var == none 一致 |
捕获异常由于提前检查
不够优雅的代码:
1
2
3
4
|
if os.path.isfile(file_path): file = open (file_path) else : # do something |
比较好的做法:
1
2
3
4
|
try : file = open (file_path) except oserror as e: # do something |
在python2.6+的里面可以更简洁:
1
|
with open (file_path) as file : |
之所以这么用,是这么写更加通用,比如file_path给你传个none就瞎了,还得判断是不是none,如果不判断,就又得抓异常,判断的话,代码有多写了很多。
类变量初始化
不要在对象的init函数之外初始化类属性,主要有两个问题
- 如果类属性更改,则初始值更改。
- 如果将可变对象设置为默认值,您将获得跨实例共享的相同对象。
错误示范(除非你想要静态变量)
1
2
3
4
5
|
``` class car( object ): color = "red" wheels = [wheel(), wheel(), wheel(), wheel()] ``` |
正确的做法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
``` class car( object ): def __init__( self ): self .color = "red" self .wheels = [wheel(), wheel(), wheel(), wheel()] ``` * * 函数默认参数 * * ``` def foo(li = []): li.append( 1 ) print (li) foo([ 2 ]) # out: [2, 1] foo([ 3 ]) # out: [3, 1] ``` |
该代码的行为与预期的一样,但如果我们不传递参数呢?
1
2
3
4
5
6
7
|
``` foo() # out: [1] as expected... foo() # out: [1, 1] not as expected... ``` |
这是因为函数参数类型是定义是确认的而不是运行时,所以在两次函数调用时,li指向的是同一个list对象,如果要解决这个问题,可以这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
``` def foo(li = none): if not li: li = [] li.append( 1 ) print (li) foo() # out: [1] foo() # out: [1] ``` |
这虽然解决了上述的问题,但,其他的一些对象,比如零长度的字符串,输出的结果就不是我们想要的。
1
2
3
4
5
6
7
8
9
10
11
|
``` x = [] foo(li = x) # out: [1] foo(li = "") # out: [1] foo(li = 0 ) # out: [1] ``` |
最常用的办法是检查参数是不是none
1
2
3
4
5
6
7
8
9
10
11
12
|
``` def foo(li = none): if li is none: li = [] li.append( 1 ) print (li) foo() # out: [1] ``` * * 在遍历时修改 * * |
for语句在遍历对象是会生成一个迭代器,如果你在遍历的过程中修改对象,会产生意想不到的结果:
1
2
3
4
5
|
alist = [ 0 , 1 , 2 ] for index, value in enumerate (alist): alist.pop(index) print (alist) # out: [1] |
第二个元素没有被删除,因为迭代按顺序遍历索引。上述循环遍历两次,结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
# iteration #1 index = 0 alist = [ 0 , 1 , 2 ] alist.pop( 0 ) # removes '0' # iteration #2 index = 1 alist = [ 1 , 2 ] alist.pop( 1 ) # removes '2' # loop terminates, but alist is not empty: alist = [ 1 ] |
如果避免这个问题了,可以创建另外一个list
1
2
3
4
5
6
7
|
alist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] for index, item in reversed ( list ( enumerate (alist))): # delete all even items if item % 2 = = 0 : alist.pop(index) print (alist) # out: [1, 3, 5, 7] |
整数和字符串定义
python预先缓存了一个区间的整数用来减少内存的操作,但也正是如此,有时候会出很奇特的错误,例如:
1
2
3
4
|
>>> - 8 is ( - 7 - 1 ) false >>> - 3 is ( - 2 - 1 ) true |
另外一个例子
1
2
3
4
|
>>> ( 255 + 1 ) is ( 255 + 1 ) true >>> ( 256 + 1 ) is ( 256 + 1 ) false |
通过不断的测试,会发现(-3,256)这区间的整数都返回true,有的甚至是(-8,257)。默认情况下,[-5,256]会在解释器第一次启动时创建并缓存,所以才会有上面的奇怪的行为。这是个很常见但很容易被忽略的一个坑。解决方案是始终使用equality(==)运算符而不是 identity(is)运算符比较值。
python还保留对常用字符串的引用,并且可以在比较is字符串的身份(即使用)时产生类似的混淆行为。
1
2
|
>>> 'python' is 'py' + 'thon' true |
python字符串被缓存了,所有python字符串都是该对象的引用,对于不常见的字符串,即使字符串相等,比较身份也会失败。
1
2
3
4
|
>>> 'this is not a common string' is 'this is not' + ' a common string' false >>> 'this is not a common string' = = 'this is not' + ' a common string' true |
所以,就像整数规则一样,总是使用equal(==)运算符而不是 identity(is)运算符比较字符串值。
列表推导和循环中的变量泄漏
有个例子:
1
2
3
|
i = 0 a = [i for i in range ( 3 )] print (i) # outputs 2 |
python2中列表推导改变了i变量的值,而python3修复了这个问题:
1
2
3
|
i = 0 a = [i for i in range ( 3 )] print (i) # outputs 0 |
类似地,for循环对于它们的迭代变量没有私有的作用域
1
2
3
4
|
i = 0 for i in range ( 3 ): pass print (i) # outputs 2 |
这种行为发生在python 2和python 3中。
为了避免泄漏变量的问题,请在列表推导和for循环中使用新的变量。
or操作符
例如
1
|
if a = = 3 or b = = 3 or c = = 3 : |
这个很简单,但是,再看一个:
1
|
if a or b or c = = 3 : # wrong |
这是由于or的优先级低于==,所以表达式将被评估为if (a) or (b) or (c == 3):。正确的方法是明确检查所有条件:
1
|
if a = = 3 or b = = 3 or c = = 3 : # right way |
或者,可以使用内置函数any()代替链接or运算符:
1
|
if any ([a = = 3 , b = = 3 , c = = 3 ]): # right |
或者,为了使其更有效率:
1
|
if any (x = = 3 for x in (a, b, c)): # right |
更加简短的写法:
1
|
if 3 in (a, b, c): # right |
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对服务器之家的支持。
原文链接:https://segmentfault.com/a/1190000019450201