2. python编程模式¶
本章介绍python编程中常用到的一些模式,面向有一定python编程经验的程序员,来提高和完善自己的python知识。
初学者最好首先参考一本python入门读本,推荐O’REILLY出版的 learning python
2.1. logging模块的使用¶
logging模块的使用能大大增强程序的可调试性,建议所有的python应用和库都使用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. 迭代器¶
迭代器可以有多种实现方式:
- 实现__iter__和next方法的object, 参见 iterator-types
- 使用yield的generator, 参见 yield expressions
- 使用built-in函数iter构造的迭代器, 参见 iter statement
迭代器的使用:
- 用在for表达式中, 参见 for statement
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¶
关于使用返回值还是使用异常的建议:
- 我们应该对使用返回值的情景和使用异常的情景进行区分,使用返回值来表达函数的状态是不推荐的,会导致上层编码风格的混乱
- 只使用返回值来传递数据,如果函数没有想要返回的值,尽量不要在函数中使用return,python会默认返回None
- 使用具体的异常类型,比如built-in的
ValueError
,AttributeError
, 不要使用Exception
, 如果需要自定义异常,将自定义的异常统一放到一个模块中,这样上层代码能方便访问你的自定义异常 - 尽量统一在上层处理异常,中间层尽量不处理异常,让异常扩散到统一处理异常的地方
2.6. property VS method¶
关于使用属性还是方法的建议:
- 属性一般意味着从内存中直接拿出之前存储的值
- 方法意味着需要一定的处理
- 如果设计上想让外部以为是属性,但需要一定的内部处理,可以使用 @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. 文本文件和解析器本身的编码¶
文本文件和解析器这种需要向用户显示信息的地方,一般都有自己的编码格式,能将二进制数据显示成文本,或将文本存储成二进制数据。
当编辑器载入文本的时候,需要知道文本的编码格式,才能正确显示。编辑器如何知道文本的编码格式呢,不同的文本格式的规则也有所不同。
- Windows文本文件会在文件开头添加一些额外的字节来标识不同的编码格式
- python脚本默认编码格式是UTF-8,可以在文本开头添加
# -*- coding: latin-1 -*-
来声明编码格式 - XML默认编码格式是UTF-8,可以在开头添加
<?xml version="1.0" encoding="ISO-8859-1"?>
来声明编码格式 - 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)