普通的Gotchas

../_images/34435688380_b5a740762b_k_d.jpg

在很大程度上,Python的目标是成为一种干净一致的语言,避免意外。然而,有几个案例可能会让新人感到困惑。

其中一些案例是故意的,但可能会让人大吃一惊。有些可以说是语言疣。一般说来,下面是一系列潜在的棘手行为,乍一看可能很奇怪,但一旦你意识到了惊喜的潜在原因,这些行为通常都是明智的。

可变默认参数

似乎是 新的Python程序员遇到的常见意外是Python在函数定义中处理可变的默认参数。

你写的东西

def append_to(element, to=[]):
    to.append(element)
    return to

你可能期望会发生什么

my_list = append_to(12)
print(my_list)

my_other_list = append_to(42)
print(my_other_list)

如果没有提供第二个参数,则每次调用函数时都会创建一个新列表,因此输出为:

[12]
[42]

到底发生了什么

[12]
[12, 42]

创建新列表 once 当函数被定义,并且在每个连续调用中使用相同的列表时。

计算Python的默认参数 once 当函数被定义时,不是每次调用函数时(比如Ruby中的函数)。这意味着,如果使用可变的默认参数并对其进行可变,那么 will 并为将来对函数的所有调用改变了该对象。

你应该怎么做

每次调用函数时,通过使用默认参数来表示没有提供参数,创建一个新对象。 (None 通常是个不错的选择)。

def append_to(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

别忘了,你通过了 list 对象作为第二个参数。

当Gotcha不是Gotcha时

有时您可以专门“利用”(读:按预期使用)此行为来维护函数调用之间的状态。这通常在编写缓存函数时完成。

后期装订闭合

另一个常见的混淆源是Python在闭包中(或在周围的全局范围内)绑定变量的方式。

你写的东西

def create_multipliers():
    return [lambda x : i * x for i in range(5)]

你可能期望会发生什么

for multiplier in create_multipliers():
    print(multiplier(2))

包含五个函数的列表,每个函数都有自己的结束符 i 使其参数相乘的变量,产生:

0
2
4
6
8

到底发生了什么

8
8
8
8
8

创建了五个函数,而不是全部都是乘法 x 到4点。

Python的闭包是 后期装订 。这意味着在调用内部函数时会查找闭包中使用的变量值。

在这里,无论何时 any 在返回的函数中,调用 i 在调用时在周围范围中查找。到那时,循环已经完成,并且 i 其最终值为4。

这件事最让人讨厌的是表面上普遍存在的错误信息,这些错误信息与此有关。 lambdas 在 Python 中。使用创建的函数 lambda 表达并不是特别的,事实上,同样的精确行为只是通过使用一个普通的 def

def create_multipliers():
    multipliers = []

    for i in range(5):
        def multiplier(x):
            return i * x
        multipliers.append(multiplier)

    return multipliers

你应该怎么做

最普遍的解决方案可以说是有点黑客。由于python在评估函数的默认参数方面的上述行为(请参见 可变默认参数 ,您可以创建一个闭包,通过使用如下的默认参数立即绑定到它的参数:

def create_multipliers():
    return [lambda x, i=i : i * x for i in range(5)]

或者,可以使用functools.partial函数:

from functools import partial
from operator import mul

def create_multipliers():
    return [partial(mul, i) for i in range(5)]

当Gotcha不是Gotcha时

有时您希望闭包以这种方式运行。后期装订在很多情况下都很好。不幸的是,创建独特函数的循环会导致打嗝。

字节码(.pyc)文件无处不在!

默认情况下,当从文件执行python代码时,Python解释器将自动将该文件的字节码版本写入磁盘,例如。 module.pyc

这些 .pyc 不应将文件签入源代码存储库。

理论上,出于性能原因,此行为在默认情况下处于启用状态。如果没有这些字节码文件,Python将在每次加载文件时重新生成字节码。

禁用字节码(.pyc)文件

幸运的是,生成字节码的过程非常快,并且在开发代码时不需要担心。

那些文件很烦人,我们把它们扔掉吧!

$ export PYTHONDONTWRITEBYTECODE=1

$PYTHONDONTWRITEBYTECODE 设置环境变量后,python将不再将这些文件写入磁盘,您的开发环境将保持良好和干净。

我建议在 ~/.profile

删除字节码(.pyc)文件

以下是删除所有这些文件的好技巧,如果它们已经存在:

$ find . -type f -name "*.py[co]" -delete -or -type d -name "__pycache__" -delete

从项目的根目录运行它,以及 .pyc 文件会突然消失。好多了。

版本控制忽略

如果你还需要 .pyc 由于性能原因,可以将文件添加到版本控制存储库的忽略文件中。流行的版本控制系统能够使用文件中定义的通配符来应用特殊规则。

忽略文件将确保匹配的文件不会签入存储库。 Git 使用 .gitignore 虽然 Mercurial 使用 .hgignore

至少您的忽略文件应该如下所示。

syntax:glob   # This line is not needed for .gitignore files.
*.py[cod]     # Will match .pyc, .pyo and .pyd files.
__pycache__/  # Exclude the whole folder

您可能希望根据需要包含更多的文件和目录。下次提交到存储库时,这些文件将不包括在内。