更好的Python 59种方式

2020-06-21 00:29:40

布雷特·斯拉特金(Brett Slatkin)著的“有效的Python:写出更好Python的59种具体方法”一书的代码示例。

目前仍在使用的Python有两个主要版本:Python2和Python3。

确保在您的系统上运行Python的命令行是您期望的版本。

更喜欢Python3作为您的下一个项目,因为这是Python社区的主要关注点。

在Python3中,字节包含8位值序列,字符串包含Unicode字符序列。字节和字符串实例不能与运算符(如>;或+)一起使用。

在Python2中,字符串包含8位值序列,Unicode包含Unicode字符序列。如果字符串只包含7位ASCII字符,则字符串和Unicode可以与运算符一起使用。

使用助手函数确保您操作的输入是您期望的字符序列类型(8位值、UTF-8编码字符、Unicode字符等)。

如果要在文件中读取或写入二进制数据,请始终使用二进制模式(如';RB&39;或';WB';)打开该文件。

Python的语法使得编写过于复杂和难以阅读的单行表达式太容易了。

将复杂的表达式移到助手函数中,特别是在需要重复使用相同逻辑的情况下。

IF/ELSE表达式提供了比在表达式中使用诸如or和adn之类的布尔运算符更具可读性的选择。

避免冗长:不要为起始索引提供0,也不要为结束索引提供序列长度。

切片允许超出界限的开始或结束索引,这使得在序列的前边界或后边界(如[:20]或[-20:])上表示切片变得容易。

分配给列表切片将用引用的内容替换原始序列中的该范围,即使它们的长度不同。

更喜欢在没有开始或结束索引的切片中使用正步幅值。如果可能,避免使用负的步幅值。

避免在单个切片中同时使用开始、结束和跨步。如果您需要所有这三个参数,请考虑执行两个赋值(一个用于切片,另一个用于跨步)或使用islice form itertools内置模块。

列表理解比map和filter内置函数更清晰,因为它们不需要额外的lambda表达式。

列表理解允许您轻松地跳过输入列表中的项目,行为映射不支持,而无需过滤器的帮助。

包含两个以上表达式的列表理解非常难以阅读,应该避免。

通过将迭代器从一个生成器表达式传递到另一个生成器表达式的for子表达式,可以组成生成器表达式。

Enumererate提供了简洁的语法,用于循环遍历迭代器,并在遍历过程中从迭代器中获取每个项目的索引。

您可以提供第二个参数进行枚举,以指定开始计数的数字(默认值为零)。

在Python3中,zip是一个生成元组的懒惰生成器。在Python2中,zip以元组列表的形式返回完整结果。

itertools内置模块中的zip_long函数允许您并行迭代多个迭代器,而不考虑它们的长度(参见第46项:使用内置算法和数据结构)。

Python有特殊的语法,允许Else块紧跟在For和While循环内部块之后。

仅当循环体未遇到BREAK语句时,循环后的ELSE块才会运行。

Try/Finally复合语句允许您运行清理代码,而不管Try块中是否引发了异常。

Else块可以帮助您最小化try块中的代码量,并直观地将成功案例与try/exclude块区分开来。

Else块可用于在成功的try块之后但在Finally块中的公共清理之前执行其他操作。

返回NONE以指示特殊含义的函数是错误优先的,因为在条件表达式中,NONE和其他值(例如,零,空字符串)等值为FALSE。

引发异常以指示特殊情况,而不是返回None。期望调用代码在记录异常时正确处理它们。

闭包函数可以引用定义它们的任何作用域中的变量。

在Python3中,使用非本地语句指示闭包何时可以修改其封闭作用域中的变量。

在Python2中,使用可变值(如单项列表)来解决非本地语句的不足。

生成器返回的迭代器生成传递给生成器函数体中的输出表达式的一组值。

生成器可以为任意大的输入产生一系列输出,因为它们的工作记忆不包括所有的输入和输出。

注意多次迭代输入参数的函数。如果这些参数是迭代器,您可能会看到奇怪的行为和遗漏的值。

Python的迭代器协议定义了容器和迭代器如何与ITER和NEXT内置函数、for循环和relateexpression交互。

通过将ITER方法实现为生成器,您可以轻松定义自己的可迭代容器类型。

如果对某个值调用ITER两次会产生相同的结果,则可以检测到该值是迭代器(而不是容器),然后可以继续执行下一个内置函数。

通过在def语句中使用*args,函数可以接受数量可变的位置参数。

您可以将序列中的项用作带*运算符的函数的位置参数。

将*运算符与生成器一起使用可能会导致程序内存不足并崩溃。

当关键字与位置参数混淆时,关键字可以清楚地说明每个参数的用途。

带有默认值的关键字参数使向函数添加新行为变得很容易,特别是当函数具有现有调用方时。

闭包函数可以引用定义它们的任何作用域中的变量。

在Python3中,使用非本地语句指示闭包何时可以修改其封闭作用域中的变量。

在Python2中,使用可变值(如单项列表)来解决非本地语句的不足。

使用仅限关键字的参数强制调用方为可能混淆的函数提供关键字参数,特别是那些接受多个布尔标志的函数。

Python2可以通过使用**kwargs并手动引发TypeError异常来模拟函数的仅关键字参数。

在需要完整类的灵活性之前,将namedtuple用于轻量级、不可变的数据容器。

当内部状态字典变得复杂时,移动记账代码以使用多个助手类。

对于Python中组件之间的简单接口,通常只需要函数,而不是定义和实例化类。

Python中对函数和方法的引用是第一类,这意味着它们可以像任何其他类型一样在表达式中使用。

通过调用特殊方法,可以像调用普通Python函数一样调用类的实例。

当您需要一个函数来维护状态时,可以考虑定义一个提供调用方法的类,而不是定义一个有状态闭包(参见第15项:";了解闭包如何与变量作用域交互)。

Python的标准方法解析顺序(MRO)解决了超类初始化顺序和菱形继承的问题。

当混合类可能需要时,在实例级别使用可插入行为来提供按类定制。

从头开始计划,允许子类更多地使用内部API和属性,而不是默认地将其锁定。

使用受保护字段的文档来引导子类,而不是试图使用私有属性强制访问控制。

只考虑使用私有属性,以避免与您无法控制的子类的命名冲突。

让您的自定义容器类型继承自定义的在集合.abc中定义的接口,以确保您的类与所需的接口和行为相匹配。

当您发现自己过度使用@Property时,可以考虑重构一个类和所有调用点。

不要纠结于试图确切地理解getAttribute是如何使用描述符协议来获取和设置属性的。

请理解,getattr在访问amissing属性时只被调用一次,而getattribute在每次访问属性时都会被调用。

通过使用来自Super()(即Object类)的方法直接访问instanceAttributes,避免getattribute和setattr中的无限递归。

使用元类来确保在定义子类时,在构造其类型的对象之前,子类的结构是良好的。

元类的新方法是在整个正文处理完class语句之后运行的。

元类允许您在程序中每次对基类进行子类化时自动运行注册代码。

使用元类进行类注册可以确保不会错过注册调用,从而避免错误。

通过使用元类和描述符,可以避免内存泄漏和弱引用模块。

子进程与Python解释器并行运行,使您能够最大限度地提高CPU使用率。

由于全局解释器锁(GIL),Python线程可以在多个CPU核心上并行使用字节码。

尽管有了GIL,Python线程仍然很有用,因为它们提供了一种看似同时做多件事情的简单方法。

使用Python线程并行执行多个系统调用。这允许您在计算的同时执行阻塞I/O。

即使Python有一个全局解释器锁,您仍然有责任保护没有锁的对象。

如果您允许多线程在没有锁定的情况下修改相同的对象,您的程序将损坏它们的数据结构。

管道是组织使用多个Python线程并发运行的工作序列的一种很好的方式。

要注意构建并发管道的许多问题:忙碌等待、停止工作进程和内存爆炸。

Queue类拥有构建健壮管道所需的所有设施:阻塞操作、缓冲区大小和连接。

协同程序提供了一种似乎同时运行数万个功能的有效方式。

在生成器中,收益表达式的值将是从外部代码传递给生成器的send方法的值。

协程为您提供了一个强大的工具,用于将程序的核心逻辑与其与周围环境的交互分离。

将CPU瓶颈转移到C扩展模块可以有效地提高性能,同时最大化您在Python代码上的投资。但是,这样做的成本很高,并且可能会引入错误。

多处理模块提供了强大的工具,可以以最小的努力并行化某些类型的Python计算。

多处理的强大功能最好是通过内置的Conccurrent.Futures模块及其简单的ProcessPoolExecutor类来实现。

在定义自己的装饰器时,请使用functools内置模块中的Wraps装饰器,以避免任何问题。

contextlib内置模块提供了一个contextmanager修饰器,使您可以轻松地在WITH语句中使用您自己的函数。

上下文管理器产生的值作为with语句的一部分提供给。由于特殊的上下文,它对于让您的代码直接访问非常有用。

使用带有PICLE的Copyreg内置模块来添加缺失的属性值,允许对类进行版本控制,并提供稳定的导入路径。

结合使用DateTime内置模块和pytz模块,可以可靠地在不同时区的时间之间进行转换。

始终以UTC表示时间,并根据当地时间进行对话,作为演示前的最后一步。

Python在模块中有内置的类型和类,几乎可以表示每种类型的数值。

Decimal类非常适合需要高精度和精确舍入行为的情况,例如货币值的计算。

Python包索引(PyPI)包含大量由Python社区构建和维护的公共包。

PIP在Python3.4和更高版本中默认安装;对于旧版本,您必须自己安装。

使用docstring为每个模块、类和函数编写文档。在代码更改时使它们保持最新。

对于模块:介绍模块的内容以及所有用户都应该知道的任何重要的类或函数。

对于类:CLASS语句后面的文档字符串中的文档行为、重要属性和子类行为。

对于函数和方法:记录def语句后面的文档字符串中的每个参数、返回值、引发的异常和其他行为。

Python中的包是包含其他模块的模块。包允许您将代码组织到独立的、不冲突的名称空间中,这些名称空间具有唯一的绝对模块名称。

通过将init.py文件添加到包含其他源文件的目录来定义简单包。这些文件成为目录软件包的子模块。软件包目录还可以包含其他软件包。

您可以通过在模块的所有特殊属性中列出其公共可见名称来为模块提供显式API。

您可以通过仅导入包的init.py文件中的公用名或使用前导下划线命名仅内部成员来隐藏包的内部实现。

当在单个团队内或在单个代码库上协作时,可能没有必要对显式API使用ALL。

中间根异常允许您在将来添加更多特定类型的异常,而不会破坏您的API使用者。

当两个模块在导入时必须互相调入时,就会发生循环依赖。它们可能会导致您的程序在启动时崩溃。

打破循环依赖的最佳方式是将相互依赖重构到依赖树底部的单独模块中。

动态导入是打破模块之间循环依赖的最简单解决方案,同时将重构和复杂性降至最低。

虚拟环境允许您使用pip在同一台机器上安装同一软件包的多个不同版本,而不会发生冲突。

您可以在pip冻结的情况下转储环境的所有要求。您可以通过将requirements.txt文件提供给pip install-r来重现该环境。

在3.4之前的Python版本中,pyvenv工具必须单独下载和安装。该命令行工具名为viralenv,而不是pyvenv。

程序通常需要在多个部署环境中运行,每个环境都有独特的假设和配置。

通过在模块作用域中使用普通的Python语句,您可以根据不同的部署环境定制模块的内容。

模块内容可以是任何外部条件的产物,包括通过sys和os模块进行的主机自省。

对内置Python类型调用print将生成值的人类可读字符串版本,这将隐藏类型信息。

对内置Python类型调用repr将生成值的可打印字符串版本。这些REPR字符串可以传递给valBuild-in函数以返回原始值。

格式字符串中的%s将生成类似于str的人类可读字符串。%r将生成类似于repr的可打印字符串。

您可以定义repr方法来自定义类的可打印表示形式,并提供更详细的调试信息。

您可以通过将TestCase子类化并根据您想要测试的行为定义一个方法来定义测试。TestCase类上的测试方法必须以单词test开头。

同时编写单元测试(针对独立的功能)和集成测试(针对交互的模块)是很重要的。

您可以使用import pdb;pdb.set_trace()语句在程序中的兴趣点直接启动Python交互式调试器。

Python调试器提示符是一个完整的Python shell,允许您检查和修改正在运行的程序的状态。

PDB shell命令允许您精确控制程序执行,允许您在检查程序状态和进行程序执行之间切换。

在优化之前,分析Python程序非常重要,因为速度减慢的来源通常是模糊不清的。

使用cProfile模块代替配置文件模块,因为它提供了更准确的配置信息。

Profile对象的runcall方法提供了隔离分析函数调用树所需的一切。

Stats对象允许您选择和打印您需要查看以了解程序性能的分析信息子集。

GC模块可以帮助您了解存在哪些对象,但它没有关于这些对象是如何分配的信息。