3.基础语法

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

  • 一般情况下尽量优先使用 import a 形式
  • 有节制地使用 from a import B 形式
  • 尽量避免使用 from a import *,因为这会污染命名空间

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

  • Python2.6 之前,Python 默认使用的相对导入
  • Python2.6 之后,绝对导入为默认的导入方式

绝对导入格式:import A.B.C 或者 form A.B import C 其中A位于项目根目录

相对导入格式:from . import Cfrom ..A import B 其中..表示上层目录

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

python 没有自增运算符

因为Python对部分常用符号和变量进行了内部存储优化,即预先分配内存,以常用整数为例,当赋值a, b=5, 5时,变量a和变量b都指向同一个内存空间,此时对变量a自增运算会导致变量b的同步自增。

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

with 表达式 [as 目标]:
    代码块

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

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

  • 加载上下文管理器对象的 __exit__() 方法以备后用

  • 调用上下文管理器对象的 __enter__() 方法

  • 如果 with 语句中设置了目标对象,则将 __enter__() 方法的返回值赋值给目标对象

  • 执行 with 中的代码块

  • 如果步骤 5 中代码正常结束,调用上下文管理其对象的 __exit__() 方法,其返回值直接忽略

  • 如果步骤 5 中代码执行过程中发生异常,调用上下文管理器对象的 __exit__() 方法,并将异常类型、值及 traceback 信息作为参数传递给 __exit__() 方法。如果 __exit__() 返回值为 false,则异常会被重新抛出;如果其返回值为 true,异常被挂起,程序继续执行。

5 建议 23:使用 else 子句简化循环(异常处理)

def print_prime(n):
    for i in xrange(2, n):
    	for j in xrange(2, i):
    		if i % j == 0:
    			break
    else:
    	print("{} is a prime number".format(i))
  • 当循环正常结束(for)和循环条件不成立(while)时 else 从句会被执行一次
  • 当循环是由 break 语句中断时,else 子句就不被执行
  • Python 的异常处理中,也提供了 else 子句语法,即try-except-else-finally 形式

6 建议 24:遵循异常处理的几点基本原则

  • 尽量只在可能抛出异常的语句块前面放入 try 语句
  • 异常应该方便定位,尽量及时处理,异常信息阅读友好
  • 无法定位或及时处理时,使用 raise 语句将异常抛出向上层传递

7 建议 25:避免 finally 中可能发生的陷阱

finally 语句中产生了新的异常或者执行了 return 或者 break 语句,那么临时保存的异常将会被丢失,从而导致异常屏蔽

8 建议 26:深入理解 None,正确判断对象是否为空

深入理解 None

  • None是一个空值对象,数据类型为 NoneType,遵循单例模式,是唯一的
  • 所有赋值为 None 的变量都相等
  • None 与任何其他非 None 的对象比较结果都为 False

判断对象是否为空

  • 优先调用内部方法__nonzero__(),用于判断变量是否为空
  • 没有方法__nonzero__(),则调用__len__() 方法判断长度是否为0
  • 两种方法都没有则返回True,即判断对象为非空

9 建议 27:连接字符串应优先使用 join 而不是 +

对长度为10w的列表进行字符串连接测速:join操作耗时约0.13s;+操作耗时约2s

N 个字符串通过+操作连接会产生N-1次申请和复制内存过程,时间复杂度近似为 $O(n^2)$ N 个字符串通过join操作连接会提前计算并一次性申请所需内存,时间复杂度近似为 $O(n)$

10 建议 28:格式化字符串时尽量使用 .format 方式而不是 %

format 方式在使用上更为灵活,可以方便地进行参数传递 % 方法在某些特殊情况下会出错,比如处理元组的时候

11 建议 29:区别对待可变对象和不可变对象

Python 中一切皆对象,每一个对象都有一个唯一的标示符(id())、类型(type())以及值。

对象根据其值能否修改分为可变对象和不可变对象,其中数字、字符串、元组属于不可变对象,字典以及列表、字节数组属于可变对象。

12 建议 30:[](){}:一致的容器初始化形式

数据量不大时,列表解析的效率更高

13 建议 31:记住函数传参既不是传值也不是传引用

正确的叫法应该是传对象(call by object)或者说传对象的引用(call-by-object-reference)

可变对象的修改在函数内外都可见,在调用者和被调用者之间共享

不可变对象的修改通过生成一个新对象然后赋值来实现

14 建议 32:警惕默认参数潜在的问题

默认参数所指向的对象在函数的多次调用中会被动态共享

解决方案是在定义的时候用 None 对象作为占位符

15 建议 33:慎用变长参数

可变长参数适合在下列情况下使用:

  • 为函数添加一个装饰器

  • 如果参数的数目不确定,可以考虑使用变长参数。

  • 用来实现函数的多态或者在继承情况下子类需要调用父类的某些方法的时候

16 建议 34:深入理解 str()repr() 的区别

  • 都可以将 Python 中的对象转换为字符串
  • __str__ 的返回结果可读性强;而__repr__ 的返回结果更准确
  • __repr__ 的返回结果一般可以使用eval() 方法还原对象
  • 定义类时,最好包含__repr__方法,方便调试;而 __str__() 方法则为可选
  • 方法既不跟特定的实例相关也不跟特定的类相关,可考虑设定为静态方法

17 建议 35:分清 staticmethod 和 classmethod 的适用场景

  • 静态方法通过装饰器@staticmethod来实现
  • 类方法通过装饰器@classmethod来实现
  • 类方法带隐含参数 (cls 参数),调用时不需要显式地提供该参数

18 细节补充

18.1 import运行机制

  • 先在 sys.modules 字典中搜索已载入内存的模块
  • 搜索 Python 内置的模块列表
  • 在 sys.path 列表定义的路径中搜索该模块
  • 找到模块则在本地作用域内初始化 module 对象
  • 找不到模块则会报错ImportError: No module named 'xxx'

细节1:sys.path中的默认路径

  1. 当前目录的路径
  2. 环境变量PYTHONPATH中指定的路径列表
  3. Python安装路径的lib目录所在路径

细节2:修改搜索路径的3种方式

  1. 动态修改sys.path列表,只会对当前项目临时生效
  2. 修改PYTHONPATH环境变量,全局永久性生效
  3. 增加.pth后缀的文件,Python会在搜索路径时,检测.pth后缀的文件并读取添加其中的路径至sys.path列表

细节3:初始化 module 对象的中间过程

  • 执行import xxx 会根据__init__.py文件控制导入的子包或者模块,若__init__.py文件不存在或者内容为空,则不主动导入子包或者模块
  • 执行from xxx import *会根据__all__变量控制导入的子包或者模块
  • import xxx只能导入模块或包(类比于文件夹的导入),而from xxx import xxx可以导入模块中的任意变量(类比于文件的导入)
  • 尽量避免模块A包含模块B,而模块B包含模块A这种情况,避免循环import问题

#import

往年同期文章