我的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 字符串对齐

让字符串“The end”左右边都将有20个“+”:

n [11]: print('The end'.center(20,'+'))
++++++The end+++++++

用直接输出字符串的方法很麻烦,也影响代码的外观。使用center方法比较容易一些。

2.2 字符串连接技巧

如下两个变量,hello和world:

hello = 'hello '
world = 'world'

如果将它们连接在一起,可以使用最先考虑到的字符串连接方式:

hello + world

但是,用”+“和的性能比较低下,用%s可以更加方便地处理好字符串的连接:

strs = '%s%s' % (hello, world)

这样做的好处:

1、可以很好控制浮点数的位数;

2、不要再对数字型用str转换了,因为%s已经自动完成该功能了

2.3 生成等宽数字

比如生成01 02 03这样格式的字符串:

for i in xrange(11):
    print('%02d' % i)

或者:

for i in xrange(11):
    print('{0:02d}'.format(i))

2.4 字符串转raw_string

Python里没有现成的函数可以把字符串转成raw string,但我们经常遇到这种问题。可以用两种方法搞定:

1、repr

a = '\\test'
a = repr(a).[1:-1]

2、eval

a = '\\test'
a = eval('"%s"'%a)

eval的太麻烦,而且有安全隐患,推荐第一种方法。

3 循环

3.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

3.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

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函数可以为cmp参数指定一个lambda表达式,告诉函数应该如何去做比较判断,由于dict对象的items函数可以返回一个列表,每一个元素都是key和value组成的元组,只用取元组第二个元素(value)做比较即可:

>>> d  = {"1": 'a', '2': 'b', '4': 'c', '3': 'd'}
>>> dict(sorted(d.items(), cmp=lambda x, y: cmp(x[1], y[1])))
{'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'})

5 列表

5.1 列表解析

Python既支持简单的表达式,也有高级的表达式。Python列表解析可以根据列表中的表达式计算结果来创建一个新的列表。例如,我需要建立一个新的列表,列表元素包含了1~100之间的所有偶数,C语言中我们可以用这样的方法完成查找偶数:

#include <stdio.h>

int main() {
    int num = 100;
    int i = 0;

    for ( ; i <= 100; i++) {
        if (i%2 == 0)
        printf("%d\n", i);
    }
    return 0;
}

同样,以这样的思维在Python中也可以这样写:

for i in range(101):
    if i%2 == 0:
        print(i)

显然Python中要敲打的代码数量比C语言代码少多了,但是我们利用列表解析还可以更加省略,甚至只需要一行代码就完成了计算功能:

>>> l = [x for x in range(101) if x%2 == 0]                                         (1)
>>> l
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100]

注意(1)那行代码,l是定义的一个新的列表,在方括号中,首先是一个循环,range(101)会将1~100的数连续存储在l列表中,每一次循环,x都会赋上列表当前的索引值,然后再将这个值与2做取模运算,如果结果为0,则证明是一个偶数,接着将x赋值给方括号中开头的x。整个顺序我通过标号标记在表达式中:

l = [x for x in range(101) if x%2 == 0]

同样,稍加改造,可以计算1~100间所有奇数:

l = [x for x in range(101) if x%2 == 1]

5.2 清空列表

假设有如下列表:

l = range(10)

清空l这个列表有三种方法:

  • 第一种方法:del l[:],这种方法最彻底,将从内存中删除。
  • 第二种方法:l = [],这种方法仅仅是把l指向到了一个新的、空的队列对象中。
  • 第三种方法:l[:] = [],这种本人觉得应该也是和第二种方法差不多。

为了节约内存的话,用第一种方法其实是不错的。

5.3 复制列表

将列表赋值给变量其实是将列表的引用(即内存地址)赋给了变量,如:

tmp = urls

就是将该列表引用的地址赋值给了tmp变量,所以无论如何改变urls,都会影响到tmp的变化。如果要想tmp复制一份不会随着urls变化而变化的列表,需要重新创建一个列表对象:

tmp = urls[:]

5.4 遍历列表时带上下标

for i, value in enumerate(["a", "b", "c"]):
    print(i, value)

5.5 平坦(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.6 统计列表各项重复次数

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})

6 生成器

6.1 生成器表达式

In [9]: l = range(10)

In [10]: it = (i for i in l if i%2 == 0)

In [11]: list(it)
Out[11]: [0, 2, 4, 6, 8]

生成器表达式(Generator Expressions)与列表解析不同,语法上是括号包围的,产生的结果是一个可迭代对象。

Generator expressions永远写到括号里面的,还可以:

In [15]: l
Out[15]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [16]: sum(i for i in l if i%2 == 0)
Out[16]: 20

6.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

可以这样调用:

In [48]: it = counter(10)

In [49]: it.next()
Out[49]: 0

In [50]: it.next()
Out[50]: 1

In [51]: it.next()
Out[51]: 2

In [52]: it.next()
Out[52]: 3

In [53]: it.send(8)
Out[53]: 8

In [54]: it.next()
Out[54]: 9

这里可以把yield放到表达式后面,调用者可以调用send方法传递一个值进去,如果没有传递的话,val默认是None

6.3 无限迭代

itertools模块里的repeat提供了一个迭代对象,每次都返回相同值:

for i in itertools.repeat(1):
    print(i)                    # 无限打印“1”

7 序列解构

>>> 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)
>>>

可迭代对象也可以把元素拆给变量。必须保证元素个数和变量数一致才可以。

8 内置变量

8.1 __debug__

是一个内置的常量,默认的值是True,如果为False,将去除代码中的assert语句。

但是不可以直接为它赋值为False,因为它是常量。只有用python -O执行的时候,它才为False

9 操作符

9.1 对象之间比较

In [1]: (1,2,3) < (4,5,6)
Out[1]: True

In [2]: (1,2,7) < (4,5,6)
Out[2]: True

In [3]: (4,2,7) < (4,5,6)
Out[3]: True

元组比较首先从序列第一个开始找,如果双方相等,就比较下一个元素,直到找到不等元素后,停止比较。也可用于列表等内置类型的比较。

10 I/O

10.1 缓存

如下代码:

import time

for i in xrange(5):
    print i,
    time.sleep(4)

你会发现变量i不是实时打印到终端的。这是因为print i,不会输出换行符,默认Linux的标准输出是缓存的,当遇到换行符或者达到一定大小之后,才会打印出来。stderr是不缓存的,一有内容则实时输出。

为了让代码实时输出,可以用sys.stdout.flush()函数

10.2 逐字节读取文件

with open('file') as f:
    for c in iter(lambda: f.read(1), ''):
        print(c)

10.3 如何获得文件大小

fstream = open('file','r')
fstream.seek(0,os.SEEK_END)    #将位置移到文件尾
fstream.tell()        #返回当前位置 就是文件尾 返回的数字就是文件的大小 单位字节

10.4 读取多个文件

from fileinput import FileInput

with FileInput(files=("/etc/hosts", "/etc/hosts.allow")) as f:
    for line in f:
        print(line)

11 调试

11.1 跟踪函数调用栈

类似执行bash -x。

详细追踪:

python -m trace --trace script.py

显示调用了哪些函数:

python -m trace --trackcalls script.py

11.2 如何获得哪个函数调用了当前函数

import traceback

def b():
    for func in traceback.extract_stack():
        print(func.name)

def call_b():
    b()

call_b()

12 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

13 Python2、Python3兼容

Python3改进了许多不合理的地方,比如在Python2.x中很多标准库的命名风格不统一,到了Python3中已经统一,例如Python2中的Queue已经改成queue、ConfigParser改成了configparser。

13.1 导入库

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

13.2 版本判断

if sys.version_info.major == 2:
    # Python2
elif sys.version_info.major == 3:
    # Python3

13.3 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='')