我的 Python Cookbook
Table of Contents
1. 开发工具
- IPython
编辑器和IDE推荐:
- Emacs
- Vim
- PyCharm
- WingIde
- Visual Studio
这里有份详细的对比:http://stackoverflow.com/questions/81584/what-ide-to-use-for-python
2. 数字
2.1. 如何给数字添加千分符号
format(12345, ',') # => 12,345
3. 字符串
3.1. 字符串对齐
让字符串“The end”左右边都将有20个“+”:
n [11]: print('The end'.center(20,'+')) ++++++The end+++++++
用直接输出字符串的方法很麻烦,也影响代码的外观。使用 center 方法比较容易一些。
3.2. 字符串连接技巧
如下两个变量,hello 和 world:
hello = 'hello ' world = 'world'
如果将它们连接在一起,可以使用最先考虑到的字符串连接方式:
hello + world
但是,用”+“和的性能比较低下,用%s可以更加方便地处理好字符串的连接:
strs = '%s%s' % (hello, world)
这样做的好处:
1、可以很好控制浮点数的位数;
2、不要再对数字型用 str 转换了,因为 %s 已经自动完成该功能了
3.3. 生成等宽数字
比如生成 01 02 03 这样格式的字符串:
for i in xrange(11): print('%02d' % i)
或者:
for i in xrange(11): print('{0:02d}'.format(i))
3.4. 字符串转 raw_string
Python 里没有现成的函数可以把字符串转成 raw string。
需要转变的原始字符串如下:
s = "\\xe4\\xbd\\xa0\\xe5\\xa5\\xbd"
我想到的是用 eval 函数来处理,Python2 实现如下:
print(eval("'%s'" % s)) # => 你好
Python3 处理时依赖 bytes 类型,因此提供给 eval 参数的字符串就显著标识未 bytes 类型:
eval("b'%s'" % s).decode("utf-8") # => 你好
使用 eval 一定要注意安全。
4. 字典
4.1. key 和 value 逆转
利用字典解析将 {"a": 1, "b": 2, "c":3} 转换成 {1: "a", 2: "b", 3: "c"}:
d = {'a': 1, 'b': 2, 'c': 3} {k:v for k,v in d.items()} # => {'a': 1, 'c': 3, 'b': 2}
Dict 的 items 方法返回一个列表,列表中每个元素都是一个包含 key 和 value 的元组:
d.items() # => [('a', 1), ('c', 3), ('b', 2)
4.2. 按 key 排序
内置的 sorted 函数可以为 key 参数指定一个 lambda 表达式,告诉函数应该如何去做比较判断,由于 dict 对象的 items 函数可以返回一个列表,每一个元素都是 key 和 value 组成的元组,只用取元组第一个元素(key)做比较即可:
d = {"1": 'a', '2': 'b', '4': 'c', '3': 'd'} dict(sorted(d.items(), key=lambda i:i[0])) # => {'1': 'a', '2': 'b', '3': 'd', '4': 'c'}
4.3. defaultdict
根据官方文档记载,defaultdict 继承自 dict,并且和 dict 有一样的方法。defaultdict 是由 C 实现的,具体代码见https://github.com/python/cpython/blob/master/Modules/_collectionsmodule.c。
defaultdict 在初始化时提供一个可调用对象,当引用的 key 不存在时,defaultdict 对象会返回调用的默认结果:
from collections import defaultdict d = defaultdict(list) d['a'] # 由于键“a”不存在,新建一个键 a,对应的值是空列表 d['b'].append(1) d # defaultdict(list, {'a': [], 'b': [1]})
初始化时也可以用 lambda 表达式:
d = defaultdict(lambda: "hello") d['a'] # => 'hello' d # => defaultdict(<function __main__.<lambda>>, {'a': 'hello'})
4.4. 合并字典并累加相同 key 的值
from collections import Counter a = {"a": 1, "b": 2, "c": 3} b = {"c": 3, "b": 1, "e": 100} merged = Counter(a) + Counter(b) print(dict(merged)) # => {'a': 1, 'b': 3, 'c': 6, 'e': 10}
用 Counter 一定要注意,value 小于等于 0 的项会被忽略掉,如果要包含值为 0 的项,只有手动实现两个字典相加。
5. 列表
5.1. 清空列表
假设有如下列表:
l = range(10)
清空l这个列表有三种方法:
- 第一种方法:del l[:],这种方法最彻底,将从内存中删除。
- 第二种方法:l = [],这种方法仅仅是把 l 指向到了一个新的、空的队列对象中。
- 第三种方法:l[:] = [],这种本人觉得应该也是和第二种方法差不多。
为了节约内存的话,用第一种方法其实是不错的。
5.2. 复制列表
将列表赋值给变量其实是将列表的引用(即内存地址)赋给了变量,如:
tmp = urls
就是将该列表引用的地址赋值给了 tmp 变量,所以无论如何改变 urls,都会影响到 tmp 的变化。如果要想 tmp 复制一份不会随着 urls 变化而变化的列表,需要重新创建一个列表对象:
tmp = urls[:]
5.3. 遍历列表时带上下标
for i, value in enumerate(["a", "b", "c"]): print(i, value)
5.4. 平坦(flatten)嵌套列表
如,列表:
[[1, 2], [3, 4], [5]]
平坦为:
[1, 2, 3, 4, 5]
实现如下:
def flatten_list(lst): """ >>> flatten_list([[1, 2], [3, 4], [5]]) [1, 2, 3, 4, 5] """ def do_flatten(): for item in lst: if hasattr(item, "__iter__") and isinstance(item, list): for sub_item in item: yield sub_item else: yield item return list(do_flatten())
5.5. 统计列表各项重复次数
from collections import Counter col = Counter(['a','b','c','a','b','b']) # => Counter({'b': 3, 'a': 2, 'c': 1}) col["xxx"] # => 0,如果访问不存在的值,Counter 不会引发 KeyError 异常,而是返回 0
自己实现版:
from collections import defaultdict count = defaultdict(int) for i in [1, 2, 3, 4, 5, 5, 3, 1]: count[i] += 1 print(count) # => defaultdict(<type 'int'>, {1: 2, 2: 1, 3: 2, 4: 1, 5: 2})
5.6. 遍历列表时一次取 n 个元素
假设列表 file_ids 保存了 n 个待下载的文件 id,但 API 限制每次最多只能下载 3 个 id 对应的文件,因此需要遍历列表,每次取 3 个元素:
file_ids = ["001", "002", "003", "004", "005", "006", "007"] step = 3 for i in range(0, len(file_ids), step): print(file_ids[i: i + step])
6. 元组
通过括号构造只有一个元素的元组:
(1,)
如果在实际编码过程中不小心遗忘最后的逗号(很有可能发生的),那最终的数据类型可就超出预期了:
type((1)) # => int type(('a')) # => str
比如我在用 Python sqlite3 库时,执行以下错误的代码:
cursor.execute("select * from tasks where task_name=?", (task_name))
报错,提示 SQL 语句只需绑定一个变量,但提供了 3 个,原因是本该给 execute 方法提供的是个元组类型,但由于少打了逗号,导致 task_name 成了一个长度为 3 的字符串:
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 3 supplied.
7. Bytes
7.1. 使用 memoryview 减少内存占用
在处理较大的字节流时,通常是将它读入到内存中,如果只需将数据其中一部分分配给新的变量,Python 内部就会创建新的对象,并分配空间,这样往往会占用很大的内存空间,Python 不像 C 语言那样对内存有很自由的掌控,但 Python 提供了 memoryview 这个内置函数来直接访问原目标的内存空间,而不是拷贝后创建新对象。
with open('/dev/random', 'rb') as f: data = f.read(1024000) some_data = data[1024:]
上方代码中,some_data 是新对象,Python 会从 1024 位置开始拷贝数据并创建新对象,因此会占用较大的内存空间,如果改用 memoryview:
some_data = memoryview(data)[1024:]
这是 some_data 和 data 是在同一片内存空间做操作,因此不会新拷贝数据,并且可以直接通过下标引用数据:
some_data[0:10].hex()
8. 循环
8.1. 如何在循环中重新迭代
例,有一个 URL 列表,需要对列表中每个 URL 都进行一次请求,若是请求失败则重试。
方法1,模拟“队列”来实现,每次从队列中取出一个 URL,若是请求失败就将 URL 重新放回队列:
urls = ["http://1.com", "http://2.com", "http://3.com"] while urls: url = urls.pop() try: do_something(url) except Exception: urls.append(url) continue
方法2,迭代中嵌套一个死循环,反复重试到成功为止:
urls = ["http://1.com", "http://2.com", "http://3.com"] for url in urls: while True: try: do_something(url) break except Exception: # 出错后反复重试 continue
8.2. try..except,发生异常后重试
下面调用 login 方法,login 方法内部以发送 HTTP 请求来获得 token:
self.token = self.login(self.username, self.password)
现在希望请求如果遇到超时,可以自动重试。实现方法和上面的类似:
while True: try: self.token = self.login(self.username, self.password) except requests.exceptions.ConnectionError: time.sleep(3) continue # 如果成功后,就跳出循环 break
9. 生成器
9.1. 生成器表达式
it = (i for i in range(10) if i%2 == 0) list(it) # => [0, 2, 4, 6, 8]
生成器表达式(Generator Expressions)与列表解析不同,语法上是括号包围的,产生的结果是一个可迭代对象。
Generator expressions 永远写到括号里面的,还可以:
sum(i for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] if i%2 == 0) # => 20
9.2. 传递值给生成器
《Functional Programming HOWTO》里“Passing values into a generator”一小节代码如下:
def counter (maximum): i = 0 while i < maximum: val = (yield i) if val is not None: i = val else: i += 1
可以这样调用:
it = counter(10) it.next() # => 0 it.next() # => 1 it.next() # => 2 it.next() # => 3 it.send(8) # => 8 it.next() # => 9
这里可以把 yield 放到表达式后面,调用者可以调用 send 方法传递一个值进去,如果没有传递的话,val 默认是 None
9.3. 无限迭代
itertools 模块里的 repeat 提供了一个迭代对象,每次都返回相同值:
for i in itertools.repeat(1): print(i) # 无限打印“1”
10. 序列解构
l = range(3) a,b,c = l a,b,c # => (0, 1, 2) it = iter(l) a,b,c = it # => a,b,c(0, 1, 2)
可迭代对象也可以把元素拆给变量。必须保证元素个数和变量数一致才可以。
10.1. 什么时候用“_”来忽略值
例,top-1m.csv 是一个记录了 Aleax 排名的文件,内容如下:
1,youtube.com 2,google.com 3,facebook.com 4,baidu.com 5,wikipedia.org 6,reddit.com 7,yahoo.com 8,google.co.in 9,qq.com 10,taobao.com ...
如果我不关心第一列数据,就能用到下划线:
with open("top-1m.csv") as f: for line in f: _, domain = line.strip().split(",") if domain in data: print(domain)
这样可以不为第一列分配一个不会使用的变量。
11. 内置变量
11.1. __debug__
是一个内置的常量,默认的值是 True,如果为 False,将去除代码中的 assert 语句。
但是不可以直接为它赋值为 False,因为它是常量。只有用 python -O 执行的时候,它才为 False
12. 操作符
12.1. 对象之间比较
(1,2,3) < (4,5,6) # True (1,2,7) < (4,5,6) # True (4,2,7) < (4,5,6) # True
元组比较首先从序列第一个开始找,如果双方相等,就比较下一个元素,直到找到不等元素后,停止比较。也可用于列表等内置类型的比较。
13. 类
13.1. 匿名类
见:https://docs.python.org/3/library/functions.html#type
type(name, bases, dict)
当给type函数提供3个参数时,就可以创建匿名类。
c = type("XX", (object, ), {"__init__": lambda: None, "a": 1}) c.a # => 1
14. 数据对象可修改问题
一定要注意哪些情况下修改传递的对象会影响到原始数据,否则很容易导致程序 bug。
第一个例子,试图将数据封装到类中保护起来:
class MemberInfo(object): def __init__(self, username, blog): self.data = { "name": username, "blog": blog } def get_raw_data(self): return self.data def print_info(self): print(self.data) info = MemberInfo("lu4nx", "www.shellcodes.org") raw_data = info.get_raw_data() raw_data["name"] = "new" info.print_info()
但是执行 info.print_info() 后,却输出:
{'name': 'new', 'blog': 'www.shellcodes.org'}
执行结果不复合我们的预期,被保护起来的数据最终被修改了。因为 get_raw_data 函数返回的数据并不是拷贝的新对象,而是指向的原始数据,对它的操作会直接影响到原始数据,所以返回时应该复制一份新的数据:
def get_raw_data(self): return self.data.copy()
同样的问题也发生在函数的参数传递中,示例代码:
def test_append(info_list): new_list = info_list.append("test") return new_list lst = ["lu4nx", "www.shellcodes.org"] new_lst = test_append(lst) print(lst) # => ['lu4nx', 'www.shellcodes.org', 'test']
test_append 函数是直接对传递的 list 对象进行 append,最终修改到了原始的 lst 变量,所以 lst 变量最终变为:
['lu4nx', 'www.shellcodes.org', 'test']
15. I/O
15.1. 缓存
如下代码:
import time for i in xrange(5): print i, time.sleep(4)
你会发现变量 i 不是实时打印到终端的。这是因为 print i, 不会输出换行符,默认 Linux 的标准输出是缓存的,当遇到换行符或者达到一定大小之后,才会打印出来。stderr 是不缓存的,一有内容则实时输出。
为了让代码实时输出,可以用 sys.stdout.flush 函数
15.2. 逐字节读取文件
with open('file') as f: for c in iter(lambda: f.read(1), ''): print(c)
15.3. 如何获得文件大小
fstream = open('file','r') fstream.seek(0,os.SEEK_END) # 将位置移到文件尾 fstream.tell() # 返回当前位置,就是文件尾。返回的数字就是文件的大小,单位字节
15.4. 读取多个文件的内容
from fileinput import FileInput with FileInput(files=("/etc/hosts", "/etc/hosts.allow")) as f: for line in f: print(line)
15.5. 实现“每读 n 行就 xx”
例如,每读500行,就将这500行的内容发送出去:
with open("/path/file") as f: data = [] while True: try: if len(data) == 3: send_to_queue(data) data.clear() line = next(f).strip() data.append(line) except StopIteration: # 发送余下的数据 send_to_queue(data) break
16. 系统
16.1. 获得当前文件所在目录(而不是当前目录)
from os.path import dirname, abspath print(dirname(abspath(__file__)))
17. 调试
17.1. 跟踪函数调用栈
类似执行 bash -x。
详细追踪:
python -m trace --trace script.py
显示调用了哪些函数:
python -m trace --trackcalls script.py
17.2. 如何获得哪个函数调用了当前函数
import traceback def b(): for func in traceback.extract_stack(): print(func.name) def call_b(): b() call_b()
18. 工具
18.1. venv
无论是生产环境还是开发环境,virtualenv 在 Python2.x 中广泛使用。Python3 中则提供了和 virtualenv 等价功能的工具——venv。
比如新建一个名为 self-python 的虚拟环境:
$ pyvenv self-python $ source self-python/bin/activate # 进入环境 (self-python) $
如果在 Python2.x 和 Python3.x 共存的系统中使用 virtualenv,也可指定版本:
$ virtualenv --python /usr/bin/python2.7 self-python
19. Python2 和 Python3
Python3 改进了许多不合理的地方,比如在 Python2.x 中很多标准库的命名风格不统一,到了 Python3 中已经统一,例如 Python2 中的 Queue 已经改成 queue、ConfigParser 改成了 configparser。
19.1. Python2 转 Python3
可以用官方提供的 2to3 这个小工具将 Python2 的代码转成 Python3。
19.2. 导入库兼容
Python3 中很多内置包名已不和 Python2 中相同,比如实现队列的包,在 Python2 中叫“Queue”,Python3 中已经规范为“queue”,在使用时可以通过异常捕获来兼容:
try: from Queue import Empty from Queue import Queue except ImportError: from queue import Empty from queue import Queue
19.3. 判断Python版本
if sys.version_info.major == 2: # Python2 elif sys.version_info.major == 3: # Python3
19.4. Python2 调用 print 函数
print 在 Python3 里已经成为一个函数了,例如:
list(map(print, [1, 2, 3])) # => # 1 # 2 # 3 # [None, None, None]
如果要在 Python2.x 中完全把 print 当作函数使用,需要显示引用:
from __future__ import print_function print(1, end='') print(2, end='')