2. python编程模式

本章介绍python编程中常用到的一些模式,面向有一定python编程经验的程序员,来提高和完善自己的python知识。

初学者最好首先参考一本python入门读本,推荐O’REILLY出版的 learning python

2.1. logging模块的使用

logging模块的使用能大大增强程序的可调试性,建议所有的python应用和库都使用logging来输出信息。

参见 python guide – logging

2.2. everything is object

Python是一门动态语言,所有的类型(函数,类,实例)都是一个object,可以被赋值给任意的变量,给python编程带来非常大的灵活性。

代码示例,动态调用函数:

functions = {
    "func1": func1,
    "func2": func2,
}

def func1():
    print(1)

def func2():
    print(2)

if __name__=="__main__":
    import sys
    func_name = sys.argv[1]
    functions[func_name]()

2.3. 迭代器和组合操作

迭代器和组合操作是python 从Haskell借鉴的语法

2.3.1. 迭代器

迭代器可以有多种实现方式:

迭代器的使用:

2.3.2. 组合操作

组合操作比一般的循环操作节省代码,而且能提高性能,组合操作会使用C的循环,而不是一般的python循环,python推荐使用组合操作来生成列表。

组合操作形式:

[ [expression] for object in iter_object [for_or_if_expression] ]

示例1:

>>> res = []
>>> for line in open('script1.py'):
... if line[0] == 'p':
... res.append(line.rstrip())
...
>>> res
['print(sys.path)', 'print(2 ** 33)']
>>> lines = [line.rstrip() for line in open('script1.py') if line[0] == 'p']
>>> lines
['print(sys.path)', 'print(2 ** 33)']

示例2:

>>> res = []
>>> for x in 'abc':
... for y in 'lmn':
... res.append(x + y)
...
>>> res
['al', 'am', 'an', 'bl', 'bm', 'bn', 'cl', 'cm', 'cn']
>>> [x + y for x in 'abc' for y in 'lmn']
['al', 'am', 'an', 'bl', 'bm', 'bn', 'cl', 'cm', 'cn']

2.4. 动态加载模块

Python通过 __import__ 函数支持动态加载模块

动态加载os模块:

os_module = __import__("os")

动态加载os.path模块:

os_path_module = __import__("os.path", fromlist=["join"])

注解

如果没有fromlist参数,即使模块名字是 os.path,也只会返回os模块:

os_module = __import__("os.path")

注解

在python 2.7和python 3之后,也可以使用 importlib.import_module() 来动态加载模块

2.5. return VS exception

关于使用返回值还是使用异常的建议:

  1. 我们应该对使用返回值的情景和使用异常的情景进行区分,使用返回值来表达函数的状态是不推荐的,会导致上层编码风格的混乱
  2. 只使用返回值来传递数据,如果函数没有想要返回的值,尽量不要在函数中使用return,python会默认返回None
  3. 使用具体的异常类型,比如built-in的 ValueError, AttributeError, 不要使用 Exception, 如果需要自定义异常,将自定义的异常统一放到一个模块中,这样上层代码能方便访问你的自定义异常
  4. 尽量统一在上层处理异常,中间层尽量不处理异常,让异常扩散到统一处理异常的地方

2.6. property VS method

关于使用属性还是方法的建议:

  1. 属性一般意味着从内存中直接拿出之前存储的值
  2. 方法意味着需要一定的处理
  3. 如果设计上想让外部以为是属性,但需要一定的内部处理,可以使用 @property 修饰

2.7. Unicode

这个关于unicode的章节是针对python 2的。

2.7.1. hex转义

str类型可以存储所有的ascii码的字符,比如英文字母,数字和一些标点符号。但对于其他语种里面的字符,比如中文字符,则需要特殊的存储处理。

str类型对于非ascii的字符,是直接存储该字符的二进制值(使用某种编码格式),可以用hex转义的str来存储这种二进制值。

示例代码如下,解析器的编码格式是UTF-8:

>>> a = "你好"
>>> a
'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> c = '\xe4\xbd\xa0\xe5\xa5\xbd'
>>> print(c)
你好

2.7.2. 编解码(encode & decode)

编码将unicode转化成某种编码格式,编码将某种编码格式转化成unicode,标准编码格式见 Standard Encodings

一般国际通用的编码格式是UTF-8,中文有时候会用gb2312,gbk编码,推荐使用UTF-8的编码格式。

示例代码如下,解析器的编码格式是UTF-8:

>>> a = "你好"
>>> a
'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> b = a.decode("utf-8")
>>> b
u'\u4f60\u597d'
>>> c = b.encode("utf-8")
>>> c
'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> a == c
True

2.7.3. 文本文件和解析器本身的编码

文本文件和解析器这种需要向用户显示信息的地方,一般都有自己的编码格式,能将二进制数据显示成文本,或将文本存储成二进制数据。

当编辑器载入文本的时候,需要知道文本的编码格式,才能正确显示。编辑器如何知道文本的编码格式呢,不同的文本格式的规则也有所不同。

  1. Windows文本文件会在文件开头添加一些额外的字节来标识不同的编码格式
  2. python脚本默认编码格式是UTF-8,可以在文本开头添加 # -*- coding: latin-1 -*- 来声明编码格式
  3. XML默认编码格式是UTF-8,可以在开头添加 <?xml version="1.0" encoding="ISO-8859-1"?> 来声明编码格式
  4. HTML默认编码格式是UTF-8,可以在开头添加 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 来声明编码格式

python解析器在不同平台上使用的编码格式也可能不同

Windows console:

>>> import locale
>>> locale.getdefaultlocale()
('en_US', 'cp1252')

Ubuntu terminal:

>>> import locale
>>> locale.getdefaultlocale()
('en_PH', 'UTF-8')

2.8. 线程和全局锁(GIL)

GIL 使得python的解析器运行在一个单线程上,简化python的解析器的实现,提高性能。 代价是python中多线程不能把load分配给CPU的多核, 参见 Thread State and the Global Interpreter Lock

所以在python执行并行任务,可以尽量使用多进程来提高效率 multiprocessing

当然,在必须使用线程的情况下,比如UI应用,可以使用python提供的线程库 threading,但是不能利用CPU的多核来提高性能。

2.9. 垃圾回收机制

Python有一个自动的垃圾回收机制,原理如下:

  • python解析器对所有的object做引用计数,每次垃圾回收时,没有被引用的object会被free
  • 当内存分配的object足够多的时候,python垃圾回收会自动运行,自动垃圾回收的频率可以通过 gc.set_threshold 设置
  • python垃圾回收机制将object分成三类, threshold0~threshold2, object的threshold越高, 执行自动垃圾回收的频率越低
  • python垃圾回收会释放循环引用的object
  • 循环引用,且包含__del__方法的object,不会被垃圾回收释放
  • 可以通过调用 gc.collect 手动运行垃圾回收

注解

频繁的垃圾回收对python应用的性能有明显影响,所以运行垃圾回收需要有一些注意:

  • 不要频繁的运行垃圾回收
  • 在不经常运行的代码后面加入手动垃圾回收
  • 在性能要求高的代码段不要运行垃圾回收,在这些代码段运行接收再进行垃圾回收

更多参考:

2.10. 面向对象设计和编程

python提供了 一套面向对象的语言特性,来支持面向对象设计和编程

2.10.1. 模板和接口

2.10.2. 动态绑定

2.10.3. 多重继承问题

python class支持多重继承,但需要注意的,使用多重继承时,python查找成员的机制。

  • 对于old-style的类(python 2中不继承object的类),查找成员的顺序是由左到右,深度优先
  • 对于new-style的类(python 2中继承object的类,python 3中所有的类都是new-style的), 查找顺序遵循 MRO(Method Resolution Order)

2.11. 修饰器

2.12. 元类

2.13. 匿名函数

2.14. 在python中使用设计模式