for i in range(len(list_)):
print(list_[i])
它能正常工作。这些代码运行起来没有问题。甚至在其他一些编程语言(比如 C 语言)中,这还是标准的 for 循环格式。
但在 Python 里,我们实际上有更好的做法。
知道吗,在 Python 里,列表对象本身就是可迭代的。使用这个特性,我们可以用 for ... in ...
语句,构建一个简洁易懂的循环:
for element in list_:
print(element)
如果你想要在 for 循环中并行遍历多个列表对象,你可以使用 zip
函数,而如果你坚持要在遍历可迭代对象的时候获取对应的索引号(例如计数),你可以使用 enumerate
函数。
错误 2:滥用全局变量
全局变量是在主代码块中以全局作用域声明的变量,而局部变量则是在某个函数中以局部作用域声明的变量。使用 global
关键字,你可以在函数内部改变全局变量的值。比如下面这个例子:
a = 1 # 在顶层定义的一个变量 a
def increment():
a += 1
return a
def increment2():
global a # 将全局变量 “a” 引入函数内部以供修改
a += 1
return a
increment()
# 返回错误信息:UnboundLocalError: local variable 'a' referenced before assignment
increment2()
# 返回: 2
许多初学者喜欢这样操作,使用 global
关键字,似乎可以省下许多在函数间传递参数的麻烦事。然而这是不对的,这让你难以追踪函数的行为。
同样,滥用全局变量还会让你的调试工作难上加难。每个函数都应该像一个独立的盒子,有着明确的功能,并且可以被重复使用。会修改全局变量的函数可能会给主脚本带来很难发现的副作用,这会让你的代码变成一团乱麻,让你无法进行调试。
在一个局部函数里修改全局变量是一个非常糟糕的编程做法。你应当将所需的变量作为参数传给函数,并且在函数结尾返回一个值给主脚本。
对于 Python 初学者来说,这个概念可能是最让人挠头的啦,毕竟在 Python 中这个特性还是挺特殊的。
在 Python 中,有两种类型的对象,可变对象和不可变对象。可变对象的状态或是内容,在运行时可以被改变,而不可变对象不可以被改变(是不是有点像绕口令)。许多自带的对象都是不可变的,包括整数 int
、浮点数 float
、字符串 string
、布尔值 bool
以及元组 tuple
对象。
st = 'A string'
st[0] = 'B' # 在 Python 中这样做会报错
另一方面,许多数据类型,比如列表 list
、集合 set
以及字典 dict
对象是可变的,也就是你可以修改这些对象内部的元素,比如修改列表对象的第一个元素: list_[0] = 'new'
。
如果一个函数的默认参数是可变对象,可能会发生一些意外情况。比如下面这个函数,它的 list_
参数的默认值是一个可变的空列表:
def foo(element, list_=[]):
list_.append(element)
return list_
让我们调用两次这个函数,而不给 list_
参数传递任何值,这样它就会使用默认值。推想过去,这两次都应该返回一个只有单个元素的列表,因为每次调用函数的时候,list_
应该都是取默认值为空才对。试试看:
a = foo(1)
# 返回 [1]
b = foo(2)
# 返回 [1,2],而不是 [2] ?这是怎么回事?
什么情况?
事实上,Python 中函数的默认参数只在函数被定义的时候进行一次求值。这意味着重复调用函数并不会重置默认参数的值,这个默认参数是会被重复使用的。
因此,如果默认参数是可变对象,它在每次函数被调用的时候都会被改变,而且这些改变的结果会影响到之后的每次调用。“标准”的做法是使用(不可变的)None
作为默认值,如下所示:
def foo(element, list_=None):
if list_ is None:
list_ = []
list_.append(element)
return list_
错误 4. 没有复制对象
复制(copy)的概念或许对初学者来说有点怪异甚至是反直觉的。举个🌰子:
你有一个列表 a = [[0,1],[2,3]]
,然后你声明一个新的列表,b = a
,现在你有了两个内容一样的列表。
那我现在是不是就可以修改 b
而不影响 a
列表中的内容了呢?
a = [[0,1],[2,3]]
b = a
b[1][1] = 100
print(a,b)
# [[0, 1], [2, 100]] [[0, 1], [2, 100]]
print(id(a)==id(b))
# True
当你使用赋值语句来“复制”一个列表时(比如 b = a
),对两个列表中任意元素的修改都会同时反映在两个对象上。赋值语句本身只是将目标对象和一个新的变量名绑定在一起,因此列表 a
和 b
在 Python 中其实对应的是同一个引用(可以通过 id()
查看)。
该怎么复制对象呢?
如果你想要“复制”对象,单独修改其中一个的值(元素)而不影响另一个,你有两种复制的办法:浅拷贝和深拷贝。让两个对象拥有不同的引用。
还是用上面的例子,你可以用 b = copy.copy(a)
来创造一个 a
的浅拷贝。浅拷贝将会创造一个新的对象,里面存储的是原来那个对象里各个元素的引用。这听起来有点复杂,让我们看看实际例子:
import copy
a = [[0,1],[2,3]]
b = copy.copy(a)
print(id(a)==id(b))
# False
b[1] = 100
print(a,b)
# [[0, 1], [2, 3]] [[0, 1], 100]
b[0][0] = -999
print(a,b)
# [[-999, 1], [2, 3]] [[-999, 1], 100]
print(id(a[0]) == id(b[0]))
# True
在创造出嵌套列表 a
的浅拷贝 b
之后,两个列表对象的引用是不一样了(id(a) != id(b)
),这里 !=
表示“不等于”。然而,它们内部的元素还保持着相同的引用,也就是 id(a[0]) == id(b[0])
。
这意味着,如果修改 b
中的元素,将不会影响到 a
,但如果你修改 b
中元素的元素,比如 b[1]
中的元素,则会影响到 a[1]
。所以这个复制方式没有达到全部深度。
简单地说,如果 b
是 a
的浅拷贝,对 b
中内嵌列表中的元素进行修改,也会影响到 a
。
如果你想要复制一个和原对象完全没有关联的对象,你需要进行深拷贝。例如,用 b = copy.deepcopy(a)
生成一个 a
的深拷贝。深拷贝将会递归地生成所有嵌套对象中的元素的拷贝。
简单地说,深拷贝对所有对象都进行复制而没有绑定。
好了,以上就是 Python 新人需要避免的 4 条常见错误。我用最崎岖的方式学到了教训,希望你不用重走这条弯路。
祝编码顺利!
(本文已投稿给「优达学城」。 原作: Eden Au 翻译:欧剃 转载请保留此信息)
编译来源: https://towardsdatascience.com/4-common-mistakes-python-beginners-should-avoid-89bcebd2c628
标签:Udacity、Translate、Python