编码时要避免的6个Python错误
作为新程序员,我们都犯过错误。但有些错误是由于不了解编程语言的特性而必然发生的。因此,我整理了一些你在使用Python编程时可能犯的最常见错误。
6. 在迭代时修改列表
在循环遍历 Python 列表时修改它听起来可能是个聪明的捷径,但这其实是一个经典的 Python 陷阱!这个偷工减料的错误可能会导致意外行为,比如跳过元素或使列表变得完全混乱。
假设我们有一个数字列表,我们想要移除所有小于5的数字。听起来很简单,对吧?
numbers = [1, 2, 6, 3, 7, 4, 8, 5]
print("Original list:", numbers)
for number in numbers:
if number < 5:
numbers.remove(number)
print("Modified list (oops!):", numbers)
如果你运行这段代码,你会发现数字 2 仍然在列表中。
但为什么呢?在这段代码中,我们正在尝试遍历 numbers 并移除小于 5 的元素。然而,当我们移除一个元素时,列表会缩小,后续元素的索引会发生改变。
这可能导致循环跳过某些元素。例如,当1被移除时,2移动到1的旧位置,但循环的内部计数器已经向前移动,可能会跳过2。
根据元素修改列表最安全和最常见的方法是对列表的副本进行迭代,或者构建一个只包含您想保留的元素的新列表。在副本方法中,我们在迭代之前创建列表的浅拷贝。这样,您可以从原始列表中删除元素,而不会影响迭代过程。
numbers = [1, 2, 6, 3, 7, 4, 8, 5]
print("Original list:", numbers)
for number in numbers[:]: # Iterate over a slice (copy) of the list
if number < 5:
numbers.remove(number)
print("Modified list (fixed with copy):", numbers)
通过使用 numbers[:],我们创建了 numbers 列表的一个 切片,这是一个浅拷贝。然后,for 循环遍历这个拷贝,完全独立于我们对原始 numbers 列表所做的任何修改。
通常,更符合Python风格的方法是创建一个新列表,仅包含您想保留的项。这种方法完全避免了就地修改,通常也更有效。
numbers = [1, 2, 6, 3, 7, 4, 8, 5]
print("Original list:", numbers)
filtered_numbers = [number for number in numbers if number >= 5]
print("Filtered list (new list method):", filtered_numbers)
在这段代码中,我们使用列表推导式从原始列表中创建一个新列表,该列表包含我们所需的元素。这也保持了原始列表的完整性。
5. 命名冲突
选择变量和文件名称可能看起来只是一个小细节,直到 Python 转身咬你(比喻意义上)。命名不当可能导致奇怪的错误、功能被遮蔽以及数小时的困惑。这通常有两种常见情况:使用 Python 关键字或内置名称作为自己的名称,以及不小心将脚本命名为现有 Python 模块的名字。
Python不会阻止你将变量命名为list、str或甚至sum。但仅仅因为你可以,并不意味着你应该这么做。
list = [1, 2, 3]
print(list)
这段代码将正常运行。但是,如果你添加另一行包含Python的list()函数,就会出现错误。
list = [1, 2, 3]
print(list)
list = list('123')
这是因为你用自己的列表对象覆盖了内置的 list() 函数。当你尝试调用 list('123') 时,Python 认为你是在尝试调用你自己的列表,而不是内置函数。所以,总是使用不与 Python 的核心功能冲突的描述性名称。
numbers = [1, 2, 3]
converted = list('123')
这不仅有助于你避免使用保留关键字,而且也是代码可读性和调试的良好实践。命名你的脚本文件也是如此。假设你创建了一个名为random.py的文件来进行随机数实验:
# random.py
import random
print(random.randint(1, 10))
但是当你运行这个时,你可能会看到一个错误,抱怨说“模块'random'没有属性'randint'。” Python正在导入你自己的文件(random.py),而不是标准库中的实际random模块。这是经典的遮蔽错误。
避免将脚本命名为现有模块,例如 random.py、math.py、os.py 或 json.py。如果您不小心这样做了,别忘了在重命名后删除相应的 .pyc 或 __pycache__ 文件夹。
4. 可变函数参数
Python 允许你为函数参数设置默认值,这非常方便。但当这些默认值是可变的(如列表或字典)时,你就会陷入一个隐蔽的bug陷阱。
默认的可变对象只在函数定义时创建一次,而不是每次调用时都创建。这意味着如果它被修改,这个变化就会在下次调用时保留下来。
def add_item_to_list(item, item_list=[]):
item_list.append(item)
return item_list
print(add_item("apple"))
print(add_item("banana"))
当你运行这段代码时,你可能会期望每次调用 add_item_to_list() 时都从一个新的空列表开始,如果你没有提供一个。然而,输出将显示一些意想不到的内容:
我们调用了这个函数两次。那么为什么在第二次调用时 "apple" 仍然存在?列表 item_list 只创建一次,因此每次调用函数时都会修改同一个列表。这会导致意外和非常多的错误行为。为了解决这个问题,你可以将 None 作为默认值,并在函数内部创建列表:
def add_item_to_list(item, item_list=None):
if item_list is None:
item_list = []
item_list.append(item)
return item_list
print(add_item("apple"))
print(add_item("banana"))
通过将默认值设置为None,每次调用该函数时都会创建一个新的列表。
3. 错误处理文件方式
在Python中处理文件是相当简单的。但代码中可能会出现一个常见的错误,那就是不关闭文件。这看起来无害,但不关闭文件可能会导致内存泄漏、文件损坏或被其他程序无法访问的锁定文件。
file = open('data.txt', 'r')
content = file.read()
print(content)
# Forgot to close the file!
保持文件打开可能会导致严重的麻烦。它甚至可能阻止其他进程删除或修改该文件。这就是为什么在完成操作后,您应该始终确保关闭文件。您可以使用close()函数来做到这一点。
file = open('data.txt', 'r')
content = file.read()
print(content)
file.close()
另一个最佳实践是使用 Python 的 with 语句,它会自动为你处理文件关闭。
with open('data.txt', 'r') as file:
content = file.read()
print(content)
with 块处理所有幕后清理工作。只要你退出该块,Python 就会为你关闭文件。这使得你不必担心手动关闭文件。
2. 从模块中导入所有内容
作为初学者,你可能会想在脚本顶部简单地添加一个 from module import *,然后就完事了。但随着代码的增长,这种捷径会成为灾难的根源。
from math import *
from custom_math import *
print(sqrt(16))
如果 math 和 custom_math 都定义了一个叫 sqrt() 的函数,那调用哪一个?Python 不会告诉你。它只是悄悄地覆盖了之前的那个。
你可能在不自觉的情况下使用了完全不同的功能。此外,在大型代码库中,调试会变成一场噩梦。你会失去对函数或类来源的追踪。这就是为什么你应该明确地导入你需要的内容。
import math
import custom_math
print(math.sqrt(16))
print(custom_math.sqrt(16))
甚至像这样:
from math import sqrt as m_sqrt
from custom_math import sqrt as c_sqrt
print(m_sqrt(16))
print(c_sqrt(16))
显式优于隐式(这是Python Zen中的核心思想)。你总是知道你在使用什么以及它来自哪里。
1. 正确的异常处理
即使是最精心编写的代码也可能遇到意想不到的问题。这些在 Python 中被称为异常。优雅地处理它们就是让你的程序健壮。然而,许多初学者在处理异常时采用了 "一网打尽 "的心态,使用宽泛的 except 子句。这可能会暂时消除错误,但会让你的代码变得脆弱、混乱和难以调试。
try:
result = 10 / 0
except:
print("Something went wrong!")
这会捕获所有内容。甚至是你没有预料到或不想处理的错误。语法错误、键盘中断、拼写错误、导入失败。Python 会用那个通用的 except 吞下它们所有。这并不是理想的做法。相反,要具体说明你要捕获的内容:
try:
result = 10 / 0
except ZeroDivisionError:
print("You can't divide by zero!")
您只捕获了您预期的错误。如果其他事情出错,Python 仍然会引发它。因此,您不会默默忽视真正的错误。您还可以聪明地处理多个异常:
try:
value = int(input("Enter a number: "))
result = 10 / value
except ValueError:
print("That's not a valid number!")
except ZeroDivisionError:
print("You can't divide by zero!")
避免单独使用except:。如果你必须捕获所有异常(很少发生),请使用except Exception:并且仍然小心处理。通过专门处理异常,你的代码会变得更可预测,更容易调试,并能够提供更有意义的反馈。
通过避免这些错误,您将引入更少的错误,这意味着需要更少的调试您的 Python 代码。如果您是 Python 新手并寻找一些资源,有许多游戏可以教授 Python。此外,您还可以进行一些项目,比如开支追踪器或创建基本脚本。




