Python 反设计模式

2025年3月17日 | 阅读 8 分钟

反模式的作用与预定义的 设计模式 相反。 我们可以通过名称来推测它的功能。 在这种方法中,对于常见的问题,有各种各样的技术,这些技术可以被形式化,并且可以成为良好的开发实践。 通常,反模式被认为是不良的。 让我们了解一些常见的反设计模式。

反模式的重要特征

这种方法有一些在编程过程中需要考虑的特征。 这些特征如下所示。

正确性

反模式将我们的代码分解成更小的块,这样我们就可以做错事。 以下是访问类外部的私有成员的简单示例。

示例

在上面的代码中,我们试图直接访问私有类成员。 这将引发错误。

可维护性

如果程序易于理解、易于调试,并且可以在需要时进行更改,则我们可以认为程序是可维护的。 反模式使程序难以维护。 导入模块可以成为可维护性的一个合适的例子。 一个模块由许多有用的预定义函数组成,用于执行特定的任务。 众所周知,Python 有许多预定义的模块和库,这使得程序易于维护。 让我们了解下面的例子。

示例 -

可读性

反模式在代码中创建了复杂性,因此代码变得难以阅读和理解。 程序员可能会犯一些常见的错误,这些错误会降低代码的可读性。 这些常见错误包括不使用 zip() 方法来迭代一对列表,请求许可而不是原谅,不使用字典推导式,使用 type() 来比较类型等。 让我们了解下面的例子。

示例 -

在上面的代码中,我们使用了 zip() 方法来比较列表对。 这是一个好习惯。 我们在列表对上使用了 zip(nums, letters) 函数的 for 循环。 Python 将自动将第一个变量分配为下一个值,将第二个变量分配为第二个列表的下一个值。

安全性

众所周知,Python 是一种高级的、动态类型的语言,这意味着用户可以更改其代码的行为,甚至可以在运行时执行代码。 使用 exec 可能会在代码中创建安全问题。 以下是一个示例。

示例 -

好的做法

性能

性能是任何 编程语言 中的一个大问题。 我们可以使用适当的函数和库来获得良好的性能。 如果我们不使用 iteritems() 来迭代大型字典,或者使用列表中的 key 来检查 key 是否包含在列表中,则性能可能会受到影响。 让我们了解下面的例子。

示例 -

在上面的代码中,我们已将列表更改为集合。 它在幕后更有效率。 在列表中,Python 会将每个数字与目标数字进行比较,但是使用集合;它可以直接访问目标数字。

Python 中一些重要的反模式

以下是 Python 中最常见的反模式。

  • 不使用 with 打开文件

不使用 with 语句打开文件是一个不好的做法。 我们需要记住通过调用 close() 函数来关闭文件。 如果我们显式关闭文件,则在资源关闭之前可能会出现异常。 因此,我们应该使用该语句打开文件,因为它实现了上下文管理协议,该协议会释放资源。 我们不需要显式关闭资源。 让我们了解下面的例子。

不好的做法

好的做法

  • 在生产代码中使用调试器

这是我们大多数人至少犯过一次的最常见的错误类型。 当我们调试代码并发现错误时,我们可能会将调试器留在生产代码中。 这会影响代码的行为。 强烈建议在检查代码之前审核代码以消除调试器的调用。

  • 不使用字面量语法初始化空列表/字典/元组

当我们通过调用 list() 而不是使用空字面量来初始化空列表时,它会使程序相对变慢。 因此,建议使用空字面量来声明列表、字典和元组。 让我们看下面的例子。

不好的做法

好的做法

  • 避免使用 get() 从字典中返回默认值

避免在代码中使用 get() 方法会影响可读性。 我们创建一个变量并为其分配默认值。 当我们检查字典中的特定键时,如果该键可用,则该键的值将分配给变量的值。 我们可以使用字典的 get() 方法轻松地做到这一点。 让我们了解下面的例子。

不好的做法

好的做法

  • 函数调用中的不一致返回类型

返回多个类型的对象会使代码混乱且难以理解。 这也可能是导致难以解决的错误的根源。 一个函数可以返回一个值而不是给定的类型(例如,整数、常量、列表、元组); 调用者检查将检查要返回的值。 建议仅返回该函数的一个类型的对象。 让我们看下面的例子。

不好的做法

好的做法

  • 不必要地使用列表/字典/集合推导式

Python 提供了诸如 all、any、enumerate、iter、itertools.cycle、itertools.accumulate 之类的函数,它们可以直接与生成器表达式一起使用。 我们不需要将推导式与这些方法一起使用。 all() 和 any() 函数也用于提供短路,但是如果使用了推导式,则会丢失此行为。 让我们看下面的例子。

不好的做法

好的做法

  • 不必要地使用 Python 生成器

我们不需要在对 dict、list 或 set 的调用中使用生成器的表达式,因为这三个都有推导式。 因此,建议使用推导式而不是生成器。 让我们了解下面的例子。

不好的做法

好的做法

  • 避免使用 item() 方法迭代字典

字典的 item() 方法返回一个带有键值元组的可迭代对象。 可以使用 for 循环解包元组。 这是一个简单、动态且推荐的方法。 让我们了解下面的例子。

不好的做法

好的做法

  • 循环中没有 break 语句的 else 子句

Python 提供了将 else 语句与循环一起使用的功能。 当循环为空并且最终在执行后循环为空时,将执行 else 块。 这似乎是预期的行为,但大多数情况下这不是预期的行为。 因此,如果我们将 else 与 Python 循环一起使用,则应在循环中使用 break 语句。

让我们了解下面的示例。

不好的做法

示例 -

输出

This list has the number
This list does NOT has the number

上面的代码演示了,我们没有在 for 循环中使用 break 语句。 现在尝试理解输出; 当我们在给定的列表中找到 searched_number 时,我们希望循环仅打印 "This list has the number," ,但它会打印两个结果。 这是因为列表变空了; else 块也被执行。

好的做法

输出

This list has the number
  • 缩进包含混合空格和制表符

这是程序员在编写代码时不太关心的最常见的反模式,尤其是初学者只关注缩进,并且他们混合了制表符和空格。 让我们了解下面的例子。

不好的做法

输出

Hello, World!
Goodbye, World!

好的做法

输出

Hello, World!
Goodbye, World!
  • 使用不符合 Python 风格的循环

通常,我们更喜欢一种明显的方式来获取列表的所有元素。 我们总是“创建一个循环,该循环使用递增索引来访问循环中列表的每个元素”。 这不是访问列表中每个元素的好方法。 我们应该使用 enumerate() 函数。 让我们了解下面的例子。

不好的做法

好的做法

  • 将 lambda 表达式分配给变量

Python lambda 函数也被称为 匿名 函数,这是相对于 def 函数 最大的优势。 作为匿名,lambda 方法可以分配给任何更大的表达式。 分配 lambda 方法会消除 lambda 函数的好处。 如果需要将 lambda 方法分配给一个变量,则使用 def 语句创建一个等效的方法。 让我们了解下面的例子。

不好的做法

好的做法

  • 方法可能是一个函数

当一个类方法不包含对类的任何引用 (self) 并且前面没有 @staticmethod 时,它 将引发“方法可能是一个函数”错误。 此错误并不严重,但我们应该检查代码以确定该函数是否真的需要成为类方法。 让我们了解下面的例子。

不好的做法

好的做法


下一主题#