Python 中的成语和反成语

  • Author

    • Moshe Zadka

本文档位于公共领域。

Abstract

可以将本文档视为本教程的补充。它显示了如何使用 Python,更重要的是,显示了如何使用 Python。

您不应使用的语言构造

尽管与其他语言相比,Python 的陷阱相对较少,但它仍然具有一些仅在极端情况下有用或很危险的构造。

来自模块导入*

内部函数定义

from module import *在函数定义内无效。尽管许多版本的 Python 不会检查无效性,但它并不能使其更有效,无非是拥有精明的律师会使人无辜。永远不要那样使用它。即使在被接受的版本中,它也会使函数的执行速度变慢,因为编译器无法确定哪些名称是本地名称,哪些是全局名称。在 Python 2.1 中,此构造会导致警告,有时甚至会导致错误。

在模块级别

虽然在模块级别使用from module import *是有效的,但通常不是一个好主意。首先,它失去了 Python 否则具有的重要属性-您可以知道每个顶级名称在您喜欢的编辑器中pass简单的“搜索”Function定义的位置。如果某些模块增加了其他Function或类,将来还会给自己带来麻烦。

在新闻组上提出的最糟糕的问题之一是为什么使用此代码:

f = open("www")
f.read()

不起作用。当然,它可以正常工作(假设您有一个名为“ www”的文件.)但是,如果模块中的某处存在语句from os import *,则该命令将不起作用。 os模块具有一个称为open()的函数,该函数返回整数。虽然非常有用,但是对内置对象进行 shade 处理是其最不有用的属性之一。

请记住,您永远无法确定模块导出的名称,因此可以取所需的信息from module import name1, name2或将其保留在模块中并根据需要进行访问import module;print module.name

一切正常

在某些情况下from module import *很好:

  • 交互式提示。例如,from math import *使 Python 成为了一个了不起的科学计算器。

  • 使用 Python 中的模块扩展 C 中的模块时。

  • 当模块宣布自己为from import *安全。

未经修饰的 exec,execfile()和朋友

“未经修饰”一词是指不带显式词典的用法,在这种情况下,这些构造会在* current *环境中评估代码。出于同样的原因,这很危险,因为from import *是危险的-它可能会越过您所依赖的变量,并使其余的代码弄乱。根本不这样做。

Bad examples:

>>> for name in sys.argv[1:]:
>>>     exec "%s=1" % name
>>> def func(s, **kw):
>>>     for var, val in kw.items():
>>>         exec "s.%s=val" % var  # invalid!
>>> execfile("handler.py")
>>> handle()

Good examples:

>>> d = {}
>>> for name in sys.argv[1:]:
>>>     d[name] = 1
>>> def func(s, **kw):
>>>     for var, val in kw.items():
>>>         setattr(s, var, val)
>>> d={}
>>> execfile("handle.py", d, d)
>>> handle = d['handle']
>>> handle()

来自模块导入名称 1,名称 2 的

这是一个“不要”,它比以前的“不要”弱得多,但是如果您没有充分的理由这样做,那么您仍然不应该这样做。通常这是一个坏主意的原因是因为您突然有了一个对象,该对象位于两个单独的命名空间中。当一个名称空间中的绑定更改时,另一名称空间中的绑定不会更改,因此它们之间将存在差异。例如,在重新加载一个模块或在运行时更改Function的定义时,就会发生这种情况。

Bad example:

# foo.py
a = 1

# bar.py
from foo import a
if something():
    a = 2 # danger: foo.a != a

Good example:

# foo.py
a = 1

# bar.py
import foo
if something():
    foo.a = 2

except:

Python 具有except:子句,该子句捕获所有异常。由于 Python 中的每个错误都会引发异常,因此使用except:会使许多编程错误看起来像是运行时问题,从而阻碍了调试过程。

以下代码显示了一个不好的例子,它是一个很好的例子:

try:
    foo = opne("file") # misspelled "open"
except:
    sys.exit("could not open file!")

第二行触发NameError,该语句被 except 子句捕获。程序将退出,并且程序打印的错误消息会让您认为问题是"file"的可读性,而实际上 true 的错误与"file"无关。

上面写的更好的方法是

try:
    foo = opne("file")
except IOError:
    sys.exit("could not open file")

运行此命令时,Python 将产生一个显示NameError的回溯,并且很明显需要修复的问题。

因为except:捕获所有异常,包括SystemExitKeyboardInterruptGeneratorExit(这不是错误,通常不应被用户代码捕获),所以使用裸露的except:几乎不是一个好主意。在需要捕获所有“常规”错误的情况下(例如在运行回调的框架中),可以捕获所有常规异常Exception的 Base Class。不幸的是,在 Python 2.x 中,第三方代码有可能引发不继承自Exception的异常,因此在 Python 2.x 中,在某些情况下,您可能不得不使用裸露的except:并手动重新引发异常你不想赶上。

Exceptions

异常是 Python 的一项有用Function。每当出现意外情况时,您都应该学会举起它们,并仅在您能对它们有所作为的地方抓住它们。

以下是一个非常流行的反习语

def get_status(file):
    if not os.path.exists(file):
        print "file not found"
        sys.exit(1)
    return open(file).readline()

考虑在调用os.path.exists()到调用open()的时间之间删除文件的情况。在这种情况下,最后一行将引发IOError。如果* file *存在但没有读取权限,则会发生相同的情况。由于在普通计算机上对现有文件和不存在文件进行测试使它看起来没有错误,因此测试结果看起来还不错,并且可以交付代码。后来,未处理的IOError(或其他一些EnvironmentError)逃逸给用户,用户可以观看丑陋的回溯。

这是一种更好的方法。

def get_status(file):
    try:
        return open(file).readline()
    except EnvironmentError as err:
        print "Unable to open file: {}".format(err)
        sys.exit(1)

在此版本中,要么打开文件并读取行(因此即使在不稳定的 NFS 或 SMB 连接上也可以使用),或者显示一条错误消息,其中提供有关打开失败原因的所有可用信息,以及应用程序被中止。

但是,即使此版本的get_status()也有太多假设-只能在运行时间较短的脚本中使用,而不是在运行时间较长的服务器中使用。当然,呼叫者可以执行类似的操作

try:
    status = get_status(log)
except SystemExit:
    status = None

但是有更好的方法。您应该try在代码中使用尽可能少的except子句-您使用的子句通常将在内部调用中执行,该调用应总是成功,或者在主函数中包含所有内容。

因此,可能是更好的get_status()版本

def get_status(file):
    return open(file).readline()

调用方可以根据需要处理异常(例如,如果它在一个循环中try多个文件),或者只是让异常向上过滤到其调用方。

但是最后一个版本仍然存在严重的问题-由于 CPython 中的实现细节,引发异常之前,除非异常处理程序完成,否则不会关闭该文件。而且,更糟糕的是,在其他实现中(例如 Jython),无论是否引发异常,它可能都不会完全关闭。

此函数的最佳版本使用open()调用作为上下文 Management 器,它将确保函数返回后立即关闭文件:

def get_status(file):
    with open(file) as fp:
        return fp.readline()

使用 Batteries

人们似乎每隔一段时间就会再次在 Python 库中写东西,通常情况很差。尽管偶然的模块的接口很差,但是使用 Python 随附的丰富标准库和数据类型通常比发明自己的模块好得多。

很少有人知道的有用模块是os.path。它始终具有适合您的 os 的正确路径算法,并且通常会比您想出的任何方法都要好得多。

Compare:

# ugh!
return dir+"/"+file
# better
return os.path.join(dir, file)

os.path中更有用的Function:basename()dirname()splitext()

由于某些原因,人们似乎还不知道许多有用的内置函数:min()max()可以找到具有相似语义的任何序列的最小值/最大值,例如,但是许多人编写了自己的max()/min()。另一个非常有用的函数是reduce(),该函数可用于对序列重复执行二进制运算,从而将其减小为单个值。例如,使用一系列乘法运算来计算阶乘:

>>> n = 4
>>> import operator
>>> reduce(operator.mul, range(1, n+1))
24

在解析数字时,请注意float()int()long()都接受字符串参数,并且会pass引发ValueError来拒绝格式错误的字符串。

使用反斜杠 continue 执行语句

由于 Python 将换行符视为语句终止符,并且由于将语句放在一行中通常很不舒服,因此许多人这样做:

if foo.bar()['first'][0] == baz.quux(1, 2)[5:9] and \
   calculate_number(10, 20) != forbulate(500, 360):
      pass

您应该意识到这很危险:\后面有一个杂散空间会使该行错误,并且众所周知,在编辑器中很难看到这些杂散空间。在这种情况下,至少是语法错误,但是如果代码是:

value = foo.bar()['first'][0]*baz.quux(1, 2)[5:9] \
        + calculate_number(10, 20)*forbulate(500, 360)

那将是微妙的错误。

通常最好在括号内使用隐式连续:

这个版本是防弹的:

value = (foo.bar()['first'][0]*baz.quux(1, 2)[5:9]
        + calculate_number(10, 20)*forbulate(500, 360))