5

Python 新人需要避免的 4 条常见错误

 2 years ago
source link: https://oicebot.github.io/2020/03/02/Four-Common-mistakes-python-beginners-should-avoid.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

发表日期:2020-03-02

Python 新人需要避免的 4 条常见错误

—— 我用最崎岖的方式学到了教训,希望你不用重走这条弯路。

作者:Eden Au


图片来源:Unsplash,摄影 Jamie Street

“面对现实吧,学编程不能有小聪明。”

上面这句话,有许多人觉得有道理。而我曾对它不屑一顾。

这是因为,在学习各种不同的编程语言时,我总能发现一些微妙的方法,来完成我想做的任何事情。我曾认为我能掌控一切。然而我错了。

你能在你的代码里做任何事,但你不应该任意乱来。

我很快就意识到,我的那些“微妙”的操作其实都是些糟糕的垃圾代码。但明明能得出正确的运行结果,为啥说是垃圾代码呢?我曾习惯于这些糟糕的编程“技巧”,直到我被一个复杂的项目狠狠摆了一道。我算是用最笨的办法学到了这个教训。

在实际开始介绍这 4 条常见的错误做法之前,我希望你已经对接下来要涉及的 Python 内置特性有了一些大概的了解。

让我们开始吧!

错误 1:不使用迭代器

基本上每个刚学 Python 的新人都干过这事。这和 ta 之前是否学过其他编程语言还没什么关系,谁都会犯错。

举个例子,假如手上有个列表 list_,你要怎么用 for 循环来按顺序读取列表中的每一个元素呢?你看,我觉得,既然 Python 中的列表是有序的,我就可以通过它的索引 i 来读取列表中的第 i 个元素,比如 list_[i]。那么,接下来我就用一个循环变量,在 for 循环中从 0 遍历到列表的总长度 len(list_),读取每一个值:

for i in range(len(list_)):
    print(list_[i])

它能正常工作。这些代码运行起来没有问题。甚至在其他一些编程语言(比如 C 语言)中,这还是标准的 for 循环格式。

但在 Python 里,我们实际上有更好的做法。

知道吗,在 Python 里,列表对象本身就是可迭代的。使用这个特性,我们可以用 for ... in ... 语句,构建一个简洁易懂的循环:

for element in list_:
    print(element)

图片来源:Unsplash,摄影 The Creative Exchange

如果你想要在 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 关键字,似乎可以省下许多在函数间传递参数的麻烦事。然而这是不对的,这让你难以追踪函数的行为。

同样,滥用全局变量还会让你的调试工作难上加难。每个函数都应该像一个独立的盒子,有着明确的功能,并且可以被重复使用。会修改全局变量的函数可能会给主脚本带来很难发现的副作用,这会让你的代码变成一团乱麻,让你无法进行调试。

在一个局部函数里修改全局变量是一个非常糟糕的编程做法。你应当将所需的变量作为参数传给函数,并且在函数结尾返回一个值给主脚本。

图片来源:Unsplash,摄影 Vladislav Klapin

*注:别把全局变量和全局常量搞混了,在大部分情况下,定义全局常量都是个很好的习惯。

错误 3:不理解可变对象

对于 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 中函数的默认参数只在函数被定义的时候进行一次求值。这意味着重复调用函数并不会重置默认参数的值,这个默认参数是会被重复使用的。

图片来源:Unsplash,摄影 Ravi Roshan

因此,如果默认参数是可变对象,它在每次函数被调用的时候都会被改变,而且这些改变的结果会影响到之后的每次调用。“标准”的做法是使用(不可变的)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),对两个列表中任意元素的修改都会同时反映在两个对象上。赋值语句本身只是将目标对象和一个新的变量名绑定在一起,因此列表 ab 在 Python 中其实对应的是同一个引用(可以通过 id() 查看)。

该怎么复制对象呢?

如果你想要“复制”对象,单独修改其中一个的值(元素)而不影响另一个,你有两种复制的办法:浅拷贝深拷贝。让两个对象拥有不同的引用。

图片来源:Unsplash,摄影 Louis Hansel

还是用上面的例子,你可以用 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]。所以这个复制方式没有达到全部深度。

简单地说,如果 ba 的浅拷贝,对 b 中内嵌列表中的元素进行修改,也会影响到 a

如果你想要复制一个和原对象完全没有关联的对象,你需要进行深拷贝。例如,用 b = copy.deepcopy(a) 生成一个 a 的深拷贝。深拷贝将会递归地生成所有嵌套对象中的元素的拷贝。

简单地说,深拷贝对所有对象都进行复制而没有绑定。

图片来源:Unsplash,摄影 Drew Coffman

好了,以上就是 Python 新人需要避免的 4 条常见错误。我用最崎岖的方式学到了教训,希望你不用重走这条弯路。

祝编码顺利!

(本文已投稿给「优达学城」。 原作: Eden Au 翻译:欧剃 转载请保留此信息)

编译来源: https://towardsdatascience.com/4-common-mistakes-python-beginners-should-avoid-89bcebd2c628

标签:UdacityTranslatePython

0 comments

Be the first person to leave a comment!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK