likes
comments
collection
share

【译】摒弃 Python 中的这 7 个坏习惯

作者站长头像
站长
· 阅读数 6

原文标题: Ditch These 7 Bad Habits in Python

原文链接: jerrynsh.com/ditch-these…

这些坏习惯我都犯过。我花了一些时间才改掉它们。

Python 以其简单和多功能而闻名。作为最常用的编程语言之一,Python 有很多优秀的教程。不幸的是,这种简单易学的语法有时会误导开发人员,尤其是那些刚接触这种语言的开发人员。编写简洁的 Python 代码可能很棘手。

在本文中,我们将深入探讨初学 Python 时可能养成的一些常见错误和坏习惯。其中包括一些我希望有人能早点告诉我的事情。

别担心,我不会用一大堆文字来烦你。相反,我将为您提供易于消化的代码片段。

简述

  1. 使用 openclose 而不是 with
  2. 使用空列表作为默认参数
  3. 避免列表生成式
  4. 滥用列表生成式
  5. 使用 Bare Except
  6. 使用 ==is 的错误方式
  7. 使用 import *

1. 使用 open & close 而不是 with 语句

当我们开始学习 Python 时,使用文件流可能是最常见的任务。大多数教程都会首先告诉我们使用下面的示例打开和关闭文件:

f = open("stocks.txt", "r")
data = f.read()
f.close() # NOTE: Always remember to close!

在这种情况下,我们经常会被提醒在使用文件后始终关闭该文件,因为:

  • 它消耗了我们有限的系统资源
  • 它可以防止我们移动或删除文件

相反,最佳实践是在处理文件操作时始终使用 Python 中的上下文管理器(with 语句)。这是一个例子:

with open("stocks.txt", "r"):    
    data = f.read()

在这里使用上下文管理器还有助于我们在出现异常时关闭文件。

当然,我们也可以使用 try-finally 方法。不过,with 语句将我们的代码简化为两行。

2. 使用空列表或字典作为默认参数

这可能是 Python 新手最困惑的问题之一。

我必须承认,多年前我刚开始学习 Python 时,这个问题让我措手不及。我还记得,当我第一次接触到默认参数的奇妙之处时,我对函数的使用简直是漫不经心。下面是一个例子:

def add_book(book_id, storage=[]):
    storage.append(book_id)
    return storage

my_book_list = add_book(1)
print(my_book_list)

my_other_book_list = add_book(2)
print(my_other_book_list)

# Expectation:
# [1]
# [2]

# Reality:
# [1]
# [1, 2] Huh, what? But they are different variables!

定义函数时,默认参数(如上例中的存储空间)只创建(即评估)一次。

由于 Python 中的列表是可变的,因此每次调用 add_book() 函数时,列表都会被修改。在这里,同一个列表被反复使用,而不是创建一个新的列表。

你应该做的是

# Do:
def add_book(book_id, storage=None):
    if storage is None:
        storage = []
        
    storage.append(book_id)
    return storage

同样的概念也适用于处理其他可变对象,例如 Python 中的字典。

3. 不使用列表生成式

列表生成式无疑是 Python 最独特的功能之一。毫无疑问,它有助于提高代码的可读性,而且通常被认为更优雅、更 “pythonic”。

Pythonic是一个形容词,描述了一种计算机编程方法,该方法与 Python 编程语言的创始哲学一致。在 Python 中有许多方法可以完成相同的任务,但通常有一种首选的方法。这种首选方式称为“pythonic”。

话虽如此,Python 新手开发人员可能很难充分利用此功能,或者至少在一开始就习惯使用它。这是一个例子:

# Don't:
def generate_fruit_basket(fruits):
    """Without list comprehension"""
    basket = []
    for fruit in fruits:
        if fruit != "grapes":
            basket.append(fruit)

# Do:
def generate_fruit_basket_lc(fruits):
    """With list comprehension"""
    return [fruit for fruit in fruits if fruit != "grapes"]

同样的逻辑也适用于在 Python 中使用字典生成式。

4. 滥用列表生成式

当我们学会了列表生成式。为了追求更 “pythonic” 的代码。就会看到过太多以各种形式滥用列表生成式的例子。

更糟糕的是,某些列表生成式的编写程度导致代码甚至不再可读,这违背了使用列表生成式的初衷。

下面举例说明你不应该这么做:

# Don't:
def get_book_price(inventory):
    return [(book, price) for book in inventory if book.startswith('A') for price in inventory[book] if price > 10]

# Perhaps slightly better...? But please don't.
def get_book_price(inventory):
    return [
        (book, price) for book in inventory
        if book.startswith('A')
        for price in inventory[book]
        if price > 10
    ]

虽然列表生成式的代码更紧凑,运行速度更快,但我们应避免编写冗长复杂的列表理解(尤其是单行),以确保代码的可读性。不要被列表生成式所困扰。

5. 使用 Bare Except

在捕获异常时,尽可能提及特定的异常,而不是使用一个简单的 except: 子句。

下面是一个 Bare Except 的例子:

# Do NOT ever use bare exception:
while True:
    try:
        input_string = input("Input something")
        converted_input = int(input_string)
        print(converted_input)
    
    except: # Can't do Ctrl + C to exit!
        print("Not a number!")

简而言之,我们不应该使用 bare except,因为它会捕获所有异常,包括一些不想捕获的异常,例如本例中的 KeyboardInterrupt(我们想退出应用程序)。

一个空的 except: 子句将捕获 SystemExitKeyboardInterrupt 异常,使得用 Control-C 中断程序变得更加困难,并且可以掩盖其他问题。如果你想捕获所有表示程序错误的异常,请使用 except Exception: 语句 (bare except等效于except BaseException:)。

不应该使用:

# Not use bare except
try:
    user = User.objects.get(pk=user_id)
    user.send_mail('Hello world')
except:
    logger.error('An error occurred!')

最佳做法:

# Use this
try:
    user = User.objects.get(pk=user_id)
    user.send_mail('Hello world')
except User.DoesNotExist:
    logger.error('The user does not exist with that ID')

除此以外,它还会使调试变成一场噩梦,因为它会导致应用程序失败,而我们却不知道原因何在。因此,请停止使用这样的异常。

6. 使用 ==is 的错误方式

但这真的很重要吗?我在野生的 Python 代码中见过太多。如果您不知道,它们是不同的。

  • is 运算符检查两个项目是否引用同一对象(即存在于同一内存位置)。又称引用相等。
  • == 运算符检查值是否相等。

这里有一个简单的例子来说明我的意思:

# NOTE: In Python everything is an object, including list
listA = [1, 2, 3]
listB = [1, 2, 3]

listA is listB # False because they are NOT the same actual object
listA == listB # True because they are equivalent

下面总结了使用 is== 运算符的注意事项:

# Do:
if foo is None:
    ...

# Don't:
if foo == None:
    ...

# Do:
if not bar:
    ...

# Don't:
if bar == False:
    ...

# Do:
if baz:
    ...

# Don't:
if baz is True:
    ...

# Don't
if baz == True:
    ...

7. 使用 import *(星号)

很多人会认为这是为了方便。但这只是懒惰的表现。

使用 import * 有可能会污染当前命名空间,因为它会导入库中的所有函数和类。

污染命名空间”是什么意思?通俗地说,就是你用 import * 导入的函数(或类)可能会与你定义的函数发生冲突。

基本上,你会面临覆盖变量或函数的风险,最终变得难以分辨哪个函数来自哪个变量或函数。

最后的思考

Python 是一种比较容易上手的编程语言。它自带的许多机制和范式极大地提高了我们作为开发人员的工作效率。

尽管如此,我们还是很容易养成用 Python 编写糟糕代码的习惯。经过反思,这篇文章也提醒了我(希望也能提醒您)远离这些不良的 Python 代码编写习惯。

这篇文章里有太多不该做的事情。

您可以做一些事情 - 开始使用 autopep8flake8pylint 甚至 mypy 等工具来保持代码质量处于最高标准。它们将帮助您根据 PEP8 等标准检查代码,从而使您成为更好的开发人员。

希望本文对您有所帮助,祝您编码愉快!