编程建议(1)
-
我们应该以一种不会对其他Python实现(比如PyPy、Jython、IronPython、Cython、Psyco等)造成不利影响的方式来编写。
例如,不要依赖CPython中对于a += b或a = a + b形式的语句在原地字符串连接上的高效实现。这种优化即使在CPython中也是脆弱的(它仅对某些类型有效),并且在不使用引用计数的实现中根本不存在。在库的性能敏感部分,应使用''.join()形式来确保跨各种实现的连接操作是线性的。
解释:
这里的其他Python实现指的是除了CPython(最广泛使用的Python解释器)之外的其他Python解释器或编译器。每个实现都有其独特的特点和优势,比如PyPy专注于提高Python代码的执行速度,Jython可以让Python代码在Java虚拟机上运行,IronPython可以让Python代码在.NET框架上运行等。
换句话说,我们应该让我们的代码尽可能具有可移植性和兼容性,以便它可以在不同的Python解释器或编译器上运行,而不会出现性能下降、功能缺失或其他问题。
-
当你需要比较一个变量是否等于
None
这样的单例时,应该总是使用is
或is not
操作符,而不是使用等号(==
)或不等号(!=
)这样的相等性操作符。
另外,当你实际上想要表达if x is not None
时,要小心不要直接写if x
——比如,在测试一个默认为None
的变量或参数是否被设置为其他值时。那个“其他值”可能有一个在布尔上下文中会被视为False
的类型(比如一个容器)!
解释:
在Python中,None
是一个特殊的单例对象,意味着整个程序中只有一个None
。因此,当你想要检查一个变量是否确实指向了None
(而不是仅仅值上相等),你应该使用is
或is not
来比较。这是因为is
操作符比较的是两个对象的身份(即它们是否是同一个对象),而==
操作符比较的是两个对象的值。虽然对于None
这样的单例来说,使用==
和is
在结果上可能是相同的,但使用is
是更加准确和推荐的方式。
在Python中,if
语句后面的表达式会被隐式地转换成布尔值。很多值在布尔上下文中都会被视为False
,除了True
、任何非零数值、非空字符串、非空容器(如列表、字典、集合等)以及非None
的对象。因此,如果你只是简单地写if x
来检查一个变量x
是否“为真”,那么当x
是一个空容器或其他在布尔上下文中被视为False
的值时,这个检查就会失败。而如果你实际上想要检查的是x
是否不是None
,那么你应该明确地写if x is not None
。这样,无论x
是什么类型的值,只要它不是None
,这个条件就会为真。
-
使用is not操作符而不是not ... is。虽然这两个表达式在功能上是相同的,但前者更易于阅读,是首选的写法。
# 正确的:
if foo is not None:
# 错误的:
if not foo is None:
解释:
在Python中,当你想要检查一个对象是否不是某个特定的单例(比如None
)时,你有两种方式来写这个逻辑:
- 使用
not ... is
的方式,比如not x is None
。 - 使用
is not
的方式,即x is not None
。
虽然从逻辑上讲,这两种写法都是正确的,并且它们会得到相同的结果,但是PEP-8建议我们使用第二种方式(is not
),因为它更加直观和易于阅读。
原因在于,当我们按照英语的阅读习惯从左到右扫描代码时,x is not None
更符合我们的思维习惯。它首先识别出x
,然后紧接着是一个清晰的否定判断is not
,最后是一个明确的比较对象None
。这种结构让我们能够快速地理解代码的含义,而不需要在脑海中重新组织语句的顺序。
相比之下,not x is None
虽然逻辑上也是正确的,但它需要我们先处理x is None
这个子表达式,然后再对结果取反。这种额外的处理步骤可能会让代码的阅读者稍微感到困惑,尤其是在更复杂的表达式中。
因此,遵循PEP-8的建议,我们应该优先使用is not
来编写这样的逻辑表达式。
-
在实现带有丰富比较功能的排序操作时,最好实现所有六个比较操作(__eq__, __ne__, __lt__, __le__, __gt__, __ge__),而不是依赖其他代码仅执行特定的比较。
为了最小化所需的工作量,functools.total_ordering() 装饰器提供了一个工具来生成缺失的比较方法。
PEP 207 指出 Python 假定反身性规则。因此,解释器可能会将 y > x 替换为 x < y,将 y >= x 替换为 x <= y,并可能交换 x == y 和 x != y 的参数。sort() 和 min() 操作保证使用 < 操作符,而 max() 函数使用 > 操作符。然而,最好实现所有六个操作,以避免在其他上下文中产生混淆。
解释:
在Python中,如果你想让你的对象支持排序和比较操作,那么最好实现所有六种比较方法(相等、不等、小于、小于等于、大于、大于等于)。不要只实现其中一部分,然后期望其他代码来填补剩下的空白。
不过,为了让你不那么辛苦,Python的functools
模块提供了一个total_ordering()
装饰器。这个装饰器可以帮你自动生成缺失的比较方法,只要你实现了__eq__
和至少一个“不严格”的比较方法(比如__lt__
或__gt__
),它就能利用这些方法来推断出其他缺失的比较方法。
Python有一个规则,叫做“反射性规则”,它允许解释器在比较操作中进行一些优化,比如交换比较运算符的两边。这意味着,即使你只实现了部分比较方法,Python也可能会在内部使用它们来进行其他类型的比较。但是,为了确保你的对象在所有情况下都能正确地进行比较,最好还是实现所有六种比较方法。特别是,当你使用sort()
、min()
或max()
等函数时,虽然它们有明确的操作符偏好,但在其他自定义的上下文中,完整的比较实现将避免潜在的混淆和错误。
总之,当你想要让你的Python对象支持排序和比较时,遵循PEP-8的建议,实现所有六种比较方法是一个好习惯。虽然functools.total_ordering()
装饰器可以帮你减轻一些负担,但理解这些比较方法如何工作以及为什么需要它们仍然是很重要的。
-
始终使用 def 语句而不是将 lambda 表达式直接绑定到标识符的赋值语句:
这意味着,当你需要定义一个函数时,应该使用 def 关键字来明确声明它,而不是通过赋值语句将 lambda 表达式的结果(即一个函数对象)赋给一个变量名。这样做可以提高代码的可读性和可维护性。
# 正确的:
def f(x): return 2*x
# 错误的:
f = lambda x: 2*x
第一种形式意味着生成的函数对象的名称具体是 'f' 而不是通用的 '<lambda>'。这在回溯和一般的字符串表示中更为有用。使用赋值语句消除了 lambda 表达式相对于显式 def 语句所能提供的唯一好处(即它可以嵌入到更大的表达式中)。
解释:
在Python中,你可以使用两种方式来定义函数:一种是使用def
关键字,后跟函数名和参数列表,以及函数体;另一种是使用lambda
关键字来创建一个匿名函数(即没有名称的函数),并将其赋值给一个变量。然而,PEP-8建议尽量使用def
语句来定义函数,因为它更加清晰和易于维护。
lambda 表达式用于创建匿名函数(即没有名称的函数)。然而,当你将 lambda 表达式的结果(即函数对象)赋值给一个变量时,虽然这个变量现在引用了这个函数对象,但函数对象本身仍然保持匿名(在内部表示中,它可能仍然显示为 '<lambda>')。相比之下,使用 def 语句定义的函数,你可以给函数指定一个有意义的名称,这个名称会在错误追踪、调试和文档等场合中使用。而lambda
表达式创建的匿名函数在错误追踪中只会显示为<lambda>
,这可能会让调试变得更加困难。此外,在字符串表示中,使用明确的函数名也更易于理解。。
虽然 lambda 表达式允许你将函数定义嵌入到更大的表达式中,但如果你只是将 lambda 表达式的结果赋值给一个变量(如 f = lambda x: x + 1),那么你就失去了 lambda 表达式的这一主要优势,因为此时你已经通过变量名 f 显式地引用了这个函数。在这种情况下,使用 def 语句会更清晰、更易于维护。
相关文章: