改善 Python 程序的 91 个建议(一)

Linux编程

点击右侧关注,免费入门到精通!

作者丨驭风者

https://zhuanlan.zhihu.com/p/26155739

第一章 引论

建议 1:理解 Pythonic 概念

Pythonic

Tim Peters 的 《The Zen of Python》相信学过 Python 的都耳熟能详,在交互式环境中输入import this可以查看,其实有意思的是这段 Python 之禅的源码:

哈哈哈,相信这是大佬在跟我们举反例吧。

书中还举了一个快排的例子:

代码风格

通过对语法、库和应用程序的理解来编写代码,充分体现 Python 自身的特色:

然后表扬了 Flask 框架,提到了 generator 之类的特性尤为 Pythonic,有个包和模块的约束:

包和模块的命名采用小写、单数形式,而且短小

包通常仅作为命名空间,如只含空的__init__.py文件

建议 2:编写 Pythonic 代码

命名的规范:

尝试去通读官方手册,掌握不断发展的新特性,这将使你编写代码的执行效率更高,推荐深入学习 Flask、gevent 和 requests。

建议 3:理解 Python 与 C 语言的不同之处

提到了三点:

Python 使用代码缩进的方式来分割代码块,不要混用 Tab 键和空格

Python 中单、双引号的使用

三元操作符:x if bool else y

建议 4:在代码中适当添加注释

这一点已经受教了,现在编写代码都会合理地加入块注释、行注释和文档注释,可以使用__doc__输出。

建议 5:通过适当添加空行使代码布局更为优雅、合理

建议 6:编写函数的 4 个原则

1.函数设计要尽量短小,嵌套层次不宜过深

2.函数申明应该做到合理、简单、易于使用

3.函数参数设计应该考虑向下兼容

4.一个函数只做一件事,尽量保证函数语句粒度的一致性

Python 中函数设计的好习惯还包括:不要在函数中定义可变对象作为默认值,使用异常替换返回错误,保证通过单元测试等。

最后还有个函数可读性良好的例子:

建议 7:将常量集中到一个文件

在 Python 中应当如何使用常量:

通过命名风格提醒使用者该变量代表常量,如常量名全部大写

通过自定义类实现常量功能:将存放常量的文件命名为constant.py,并在其中定义一系列常量

其他模块中引用这些常量时,按照如下方式进行即可:

第二章 编程惯用法

建议 8:利用 assert 语句来发现问题

运行是加入-O参数可以禁用断言。

建议 9:数据交换的时候不推荐使用中间变量

对于表达式x, y = y, x,在内存中执行的顺序如下:

先计算右边的表达式y, x,因此先在内存中创建元组(y, x),其标识符和值分别为y, x及其对应的值,其中y和x是在初始化已经存在于内存中的对象

计算表达式左边的值并进行赋值,元组被依次分配给左边的标识符,通过解压缩,元组第一标识符y分配给左边第一个元素x,元组第二标识符x分配给左边第一个元素y,从而达到交换的目的

下面是通过字节码的分析:

建议 10:充分利用 Lazy evaluation 的特性

哈哈哈,我猜到肯定是生成器实现菲波拉契序列的例子,不过对比我写的版本,唉。。。

建议 11:理解枚举替代实现的缺陷

利用 Python 的动态特征,可以实现枚举:

建议 12:不推荐使用 type 来进行类型检查

作为动态语言,Python 解释器会在运行时自动进行类型检查并根据需要进行隐式类型转换,当变量类型不同而两者之间又不能进行隐式类型转换时便抛出TypeError异常。

所以实际应用中,我们常常需要进行类型检查,但是不推荐使用type(),因为基于内建类型扩展的用户自定义类型,type()并不能准确返回结果:

我们可以使用isinstance来检查:isinstance(object, classinfo)

建议 13:尽量转换为浮点类型后再做除法

建议 14:警惕 eval() 的安全漏洞

eval(expression[, globals[, locals]])将字符串 str 当成有效的表达式来求值并返回计算结果,globas为字典形式,locals为任何映射对象,它们分别表示全局和局部命名空间,两者都省略表达式将在调用的环境中执行,为什么需要警惕eval()呢:

如果确实需要使用eval,建议使用安全性更好的ast.literal_eval。

建议 15:使用 enumerate() 获取序列迭代的索引和值

建议 16:分清 == 与 is 的适用场景

操作符意义isobject identity==equal

is的作用是用来检查对象的标示符是否一致,也就是比较两个对象在内存中是否拥有同一块内存空间,相当于id(x) == id(y),它并不适用于判断两个字符串是否相等。==才是用来判断两个对象的值是否相等,实际是调用了内部的__eq__,所以a==b相当于a.__eq__(b),也就是说==是可以被重载的,而is不能被重载。

咦~怎么上例中的a, b又是“同一对象”了?这跟 Python 的 string interning 机制有关,为了提高系统性能,对于较小的字符串会保留其值的一个副本,当创建新的字符串时直接指向该副本,所以a和b的 id 值是一样的,同样对于小整数[-5, 257)也是如此:

建议 17:考虑兼容性,尽可能使用 Unicode

我之前也总结过编码的问题。由于最早的编码是 ASCII 码,只能表示 128 个字符,显然这对其它语言编码并不适用,Unicode就是为了不同的文字分配一套统一的编码。

建议 18:构建合理的包层次来管理 module

本质上每一个 Python 文件都是一个模块,使用模块可以增强代码的可维护性和可重用性,在较大的项目中,我们需要合理地组织项目层次来管理模块,这就是包(Package)的作用。

一句话说包:一个包含__init__.py 文件的目录。包中的模块可以通过.进行访问,即包名.模块名。那么这个__init__.py文件有什么用呢?最明显的作用就是它区分了包和普通目录,在该文件中申明模块级别的 import 语句从而变成了包级别可见,另外在该文件中定义__all__变量,可以控制需要导入的子包或模块。

这里给出一个较为合理的包组织方式,是FlaskWeb 开发:基于Python的Web应用开发实战一书中推荐而来的:

第三章基础语法

建议 19:有节制地使用 from...import 语句

Python 提供三种方式来引入外部模块:import语句、from...import语句以及__import__函数,其中__import__函数显式地将模块的名称作为字符串传递并赋值给命名空间的变量。

使用import需要注意以下几点:

优先使用import a的形式

有节制地使用from a import A

尽量避免使用from a import *

在sys.modules中搜索该模块是否存在,如果存在就导入到当前局部命名空间,如果不存在就为其创建一个字典对象,插入到sys.modules中

加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译

执行动态加载,在当前命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中

从上可以看出,对于用户自定义的模块,import 机制会创建一个新的 module 将其加入当前的局部命名空间中,同时在 sys.modules 也加入该模块的信息,但本质上是在引用同一个对象,通过test.py所在的目录会多一个字节码文件。

建议 20:优先使用 absolute import 来导入模块

建议 21: i+=1 不等于 ++i

首先++i或--i在 Python 语法上是合法,但并不是我们通常理解的自增或自减操作:

原来+或-只表示正负数符号。

建议 22:使用 with 自动关闭资源

对于打开的资源我们记得关闭它,如文件、数据库连接等,Python 提供了一种简单优雅的解决方案:with。

先来看with实现的原理吧。

with的实现得益于一个称为上下文管理器(context manager)的东西,它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议,即对象中定义了__enter__()和__exit__(),任何实现了上下文协议的对象都可以称为一个上下文管理器:

__enter__():返回运行时上下文相关的对象

__exit__(exception_type, exception_value, traceback):退出运行时的上下文,处理异常、清理现场等

包含with语句的代码块执行过程如下:

1.计算表达式的值,返回一个上下文管理器对象

2.加载上下文管理器对象的__exit__()以备后用

3.调用上下文管理器对象的__enter__()

4.将__enter__()的返回值赋给目标对象

5.执行代码块,正常结束调用__exit__(),其返回值直接忽略,如果发生异常,会调用__exit__()并将异常类型、值及 traceback 作为参数传递给__exit__(),__exit__()返回值为 false 异常将会重新抛出,返回值为 true 异常将被挂起,程序继续执行

于此,我们可以自定义一个上下文管理器:

Python 还提供contextlib模块,通过 Generator 实现,其中的 contextmanager 作为装饰器来提供一种针对函数级别上的上下文管理器,可以直接作用于函数/对象而不必关心__enter__()和__exit__()的实现。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181027B1FVE200?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券