Python笔记(三)-- Important Warning

本篇整理下python中容易出错的那些坑。
在学习Python的过程中,发现其很多与java不同的地方,很多人并没有仔细研读过python,只是大概地看下相关的语法就开始上手写代码去了,最容易被忽视的就是简单的for,if等控制语句,实际上python的控制语句与java等高级语言是不同的,所谓失之毫厘差之千里,请看下面的例子。

for循环操作可变序列

1
2
3
4
5
6
7
8
9
10
11
12
13
a = [-1, 2, -4, -3, -1, -2, 3]
print(a)
for x in a:
if x < 0:
a.remove(x)
print(a) # [2, -3, -2, 3]
print("=========================")
b = [-1, 2, -4, -3, -1, -2, 3]
print(b)
for x in b[:]:
if x < 0:
b.remove(x)
print(b) # [2, 3]

Note There is a subtlety when the sequence is being modified by the loop (this can only occur for mutable sequences, i.e. lists). An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. When this counter has reached the length of the sequence the loop terminates. This means that if the suite deletes the current (or a previous) item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated). Likewise, if the suite inserts an item in the sequence before the current item, the current item will be treated again the next time through the loop. This can lead to nasty bugs that can be avoided by making a temporary copy using a slice of the whole sequence.
简而言之,循环时指针会移动,所以直接删除是不安全的。而解决办法也很精妙(真是完美契合subtlety这个词),使用b[:]会在内存中临时复制出一个b,在b[:]中循环,在原本的b上remove。

for else

我问了身边三个会python的程序猿,他们都震惊地表示:还有这种操作?!然而官方文档写得很清楚,for else,try else都是python的独特的正确的语法。

1
2
for_stmt ::= "for" target_list "in" expression_list ":" suite
["else" ":" suite]

A break statement executed in the first suite terminates the loop without executing the else clause’s suite. A continue statement executed in the first suite skips the rest of the suite and continues with the next item, or with the else clause if there is no next item.

也就是说,正常情况下else语句会在循环完成(遍历完整个expression_list)后执行。但是如果有break发生时,else语句不执行,也就是说break会同时跳出for和else的整程序组(suite)。而continue会在循环中跳过else语句,然后在循环完毕后执行else语句

示例:

1
2
3
4
5
6
7
8
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, 'equals', x, '*', n//x)
break
else:
# loop fell through without finding a factor
print(n, 'is a prime number')

换个说法再来解释一遍:

Loop statements may have an else clause; it is executed when the loop terminates through exhaustion of the list (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement.

你能不用for else语句来实现相同的功能么?(if x == n-1:)

默认参数函数

Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:

1
2
3
4
5
6
7
8
9
10
11
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
# 输出结果为
[1]
[1, 2]
[1, 2, 3]

官方的解释不够详细,廖雪峰的解释如下:
Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
所以,定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上面的例子,我们可以用None这个不变对象来实现。
解决办法:

1
2
3
4
5
6
7
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
print(f(1), f(2), f(3)) # [1] [2] [3]

参考文章

  1. 官方文档–More Control Flow Tools