The Python Standard Library.
Python提供了许多常用的标准库,这些标准库无需安装即可直接使用。本文介绍一些常用的标准库:
- 数据类型模块:
- heapq:堆模块
- collections:集合类
- sortedcontainers:有序列表/字典/集合
- 文件访问模块:
- os:目录和文件操作
- shutil:文件和文件夹的高级操作
- 编程控制模块:
- pickle:序列化
- json:JSON格式转换
- unittest:单元测试
- doctest:文档测试
- datatime:处理日期和时间
- time:返回时间
- timeit:统计运行时间
- bisect:查找和插入
- argparse:解析命令行参数
- itertools:迭代器
(1)数据类型模块
⚪ heapq:堆模块
数据结构堆(heap)是一种优先队列。能够以任意顺序添加对象,并随时(可能是在两次添加对象之间)找出(并删除)最小的元素。
Python没有独立的堆类型,而只有一个包含一些堆操作函数的模块。这个模块名为heapq(其中的q表示队列)。它包含6个函数,其中前4个与堆操作直接相关。必须使用列表来表示堆对象本身。

⚪ collections:集合类
collections是Python内建的一个集合模块,提供了许多有用的集合类。
⭐ namedtuple
namedtuple定义了一种数据类型,它具备tuple的不变性,又可以根据属性来引用。namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
⭐ deque
list是线性存储,数据量大的时候,插入和删除效率很低。deque是高效实现$O(1)$的插入和删除操作的双端队列(double-ended queue),适合用于队列和栈。deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。
>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q.pop()
>>> q.popleft()
⭐ defaultdict
使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict。注意默认值是调用函数返回的,而函数在创建defaultdict对象时传入。除了在Key不存在时返回默认值,defaultdict的其他行为跟dict是完全一样的。
>>> from collections import defaultdict
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key'] # key不存在,返回默认值
'N/A'
⭐ OrderedDict
使用dict时,Key是无序的;在对dict做迭代时,无法确定Key的顺序。如果要保持Key的顺序,可以用OrderedDict。OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:
>>> from collections import OrderedDict
>>> od = OrderedDict([('a', 1), ('c', 2), ('b', 3)])
>>> od # OrderedDict的Key是有序的
OrderedDict([('a', 1), ('c', 2), ('b', 3)])
⭐ ChainMap
ChainMap可以把一组dict串起来并组成一个逻辑上的dict。ChainMap本身也是一个dict,但是查找的时候,会按照顺序在内部的dict依次查找。
应用场景:应用程序往往都需要传入参数,参数可以通过命令行传入,可以通过环境变量传入,还可以有默认参数。可以用ChainMap实现参数的优先级查找,即先查命令行参数,如果没有传入,再查环境变量,如果没有,就使用默认参数。
from collections import ChainMap
# 构造缺省参数:
defaults = {'color': 'red'}
# 组合ChainMap:
combined = ChainMap(dict1, dict2, defaults)
# 打印参数:
print('color=%s' % combined['color'])
⭐ Counter
Counter是一个简单的计数器,用于计算值的出现次数,比如可以统计列表元中素或字符串中字符出现的个数。Counter实际上也是dict的一个子类,返回一个每个字符出现次数的字典。
>>> from collections import Counter
>>> c = Counter()
>>> for ch in 'programming':
... c[ch] = c[ch] + 1
>>> c.update('hello') # 也可以一次性update
>>> c
Counter({'r': 2, 'o': 2, 'g': 2, 'm': 2, 'l': 2, 'p': 1, 'a': 1, 'i': 1, 'n': 1, 'h': 1, 'e': 1})
⚪ sortedcontainers:有序列表/字典/集合
sortedcontainers提供了有序列表(优先队列)、有序字典(treemap)和有序集合(treeset)这三种数据结构,并且默认从小到大排列。
⭐ SortedList:优先队列
from sortedcontainers import SortedList
sl = SortedList([])
# 增加元素
sl.add(item)
# 查询元素
sl[idx]
# 删除元素
sl.pop(idx)
# 插入位置,若没有找到则返回len(sl)
sl.bisect_left(item) # lowerbound(>=item)
sl.bisect_right(item) # upperbound(>item)
⭐ SortedDict:TreeMap
from sortedcontainers import SortedDict
sd = SortedDict({})
# 增加元素
sd[key] = value
# 查询元素
sd[key] # 根据key查询
sd.peekitem(idx) # 根据idx查询,返回编号为idx的(key, val)对
# 删除元素
sd.pop(key) # 根据key删除
sd.popitem(idx) # 根据idx删除,删除编号为idx的(key, val)对
# 插入位置
sd.bisect_left(key) # lowerbound(>=key)
sd.bisect_right(key) # upperbound(>key)
⭐ SortedSet:TreeSet
from sortedcontainers import SortedSet
ss = SortedSet({})
# 增加元素
ss.add(item)
# 查询元素
ss[idx]
# 删除元素
ss.remove(val) # 根据val删除
ss.pop(idx) # 根据idx删除
# 插入位置
ss.bisect_left(key) # lowerbound(>=key)
ss.bisect_right(key) # upperbound(>key)
(2)文件访问模块
⚪ os:目录和文件操作
Python的os模块封装了操作系统的目录和文件操作。os模块是通过直接调用操作系统提供的接口函数实现这些功能的。
使用os模块的基本功能:
>>> import os
>>> os.name # 操作系统类型
'posix' # posix说明系统是Linux、Unix或Mac OS X,nt是Windows系统。
>>> os.uname() # 获取详细的系统信息,在Windows上不提供
>>> os.environ # 查看操作系统中定义的环境变量
>>> os.environ.get('x', 'default') # 要获取某个环境变量的值
操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中。
# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users'
# 判断路径是否存在:
>>> os.path.exists(file_path)
# 把完整路径表示出来:
>>> os.path.join('/Users', 'testdir')
'/Users/testdir'
# 拆分路径可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:
>>> os.path.split('/Users/testdir/file.txt')
('/Users/testdir', 'file.txt')
# 直接得到文件扩展名
>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')
# 创建一个目录:
>>> os.mkdir('/Users/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/testdir')
# 返回指定的文件夹包含的文件或文件夹的名字的列表
>>> name = os.listdir(path)
# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')
⚪ shutil:文件和文件夹的高级操作
shutil(shell utilities)提供了文件和文件夹的高级操作,如文件复制和删除。
import shutil
# 复制文件
shutil.copy(source_path, target_path)
# 复制文件夹:
shutil.copytree(source_path, target_path) # 若目标文件夹已存在则报错 FileExistsError
# 移动文件:
shutil.move(source_path, target_path) # 若目标文件夹不存在则报错 FileNotFoundError
# 删除文件夹:
shutil.rmtree(file_path)
下面的程序是将一个文件夹中的所有png文件复制到另一个文件夹中:
import os
import shutil
origin_path = 'origin'
target_path = 'target'
names = os.listdir(origin_path)
for name in names:
'png' == name.split('.')[-1]:
shutil.copy(os.path.join(origin_path,name), os.path.join(target_path,name))
(3)编程控制模块
⚪ pickle:序列化
把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。Python提供了pickle模块来实现序列化。
pickle.dumps()方法把任意对象序列化成一个bytes,然后就可以把这个bytes写入文件。或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object:
>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
# or
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()
当要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象。
>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
⚪ json:JSON格式转换
如果要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如序列化为JSON,JSON表示的对象就是标准的JavaScript语言的对象。因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,而且可以直接在Web页面中读取,非常方便。
Python内置的json模块提供了非常完善的Python对象到JSON格式的转换。由于JSON标准规定JSON编码是UTF-8,所以总是能正确地在Python的str与JSON的字符串之间转换。
dumps()方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object。
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
要把JSON反序列化为Python对象,用loads()或者对应的load()方法,前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> json.loads(json_str)
当默认的序列化或反序列机制不满足要求时,可以传入更多的参数来定制序列化或反序列化的规则。比如默认情况下dumps()方法不知道如何将一个类实例变为一个JSON的对象。可选参数default就是把任意一个对象变成一个可序列为JSON的对象:
json.dumps(s, default=lambda obj: obj.__dict__)
⚪ unittest:单元测试
Python自带的unittest模块可以实现单元测试,即对一个模块、一个函数或者一个类来进行正确性检验的测试工作。单元测试可以有效地测试某个程序模块的行为,测试用例要覆盖常用的输入组合、边界条件和异常。
以测试为驱动的开发模式(TDD:Test-Driven Development)最大的好处就是确保一个程序模块的行为符合设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。
编写单元测试时,需要编写一个测试类,从unittest.TestCase继承。以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。
对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,只需要调用这些方法就可以断言输出是否是所期望的。最常用的断言就是assertEqual(),判断函数返回的结果是否为某值;另一种重要的断言就是期待抛出指定类型的Error。
import unittest
class TestDict(unittest.TestCase):
def test_init(self):
d = dict('a'=1)
self.assertEqual(d['a'], 1)
self.assertTrue(isinstance(d, dict))
def test_key(self):
d = dict()
d['key'] = 'value'
self.assertEqual(d['key'], 'value')
def test_keyerror(self):
d = dict()
with self.assertRaises(KeyError):
value = d['empty']
编写好单元测试后就可以运行单元测试。最简单的运行方式是在test.py最后加上两行代码:
if __name__ == '__main__':
unittest.main()
这样就可以把test.py当做正常的python脚本运行。另一种方法是在命令行通过参数-m unittest直接运行单元测试:
$ python -m unittest test
可以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。设想测试需要启动一个数据库,这时就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,不必在每个测试方法中重复相同的代码。
class TestDict(unittest.TestCase):
def setUp(self):
print('setUp...')
def tearDown(self):
print('tearDown...')
⚪ doctest:文档测试
Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用…表示中间一大段烦人的输出。
# test.py
def fact(n):
'''
Calculate 1*2*...*n
>>> fact(10)
3628800
>>> fact(-1)
Traceback (most recent call last):
...
ValueError
'''
if n < 1:
raise ValueError()
if n == 1:
return 1
return n * fact(n - 1)
if __name__ == '__main__':
import doctest
doctest.testmod()
当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。所以不必担心doctest会在非测试环境下执行。
⚪ datatime:处理日期和时间
datetime是Python处理日期和时间的标准库。注意到datetime是模块,datetime模块还包含一个datetime类。
>>> from datetime import datetime
>>> now = datetime.now() # 返回当前日期和时间,其类型是datetime
>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
在计算机中,时间实际上是用数字表示的。把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数,称为timestamp。Python的timestamp是一个浮点数,整数位表示秒。
>>> dt.timestamp() # 把datetime转换为timestamp
>>> datetime.fromtimestamp(t) # 把timestamp转换为datetime(本地时区)
>>> datetime.utcfromtimestamp(t) # 把timestamp转换为datetime(零时区)
很多时候用户输入的日期和时间是字符串,要实现str和datetime之间的转换。
# str转换为datetime
>>> cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')
# datetime转换为str
>>> now.strftime('%a, %b %d %H:%M')
对日期和时间进行加减实际上就是把datetime往后或往前计算,得到新的datetime。加减可以直接用+和-运算符,不过需要导入timedelta这个类:
>>> from datetime import datetime, timedelta
>>> now = datetime.now()
>>> now + timedelta(days=2, hours=12)
本地时间是指系统设定时区的时间,例如北京时间是UTC+8:00时区的时间,而UTC时间指UTC+0:00时区的时间。一个datetime类型有一个时区属性tzinfo,但是默认为None。
>>> from datetime import datetime, timedelta, timezone
# 拿到UTC时间,并强制设置时区为UTC+0:00:
>>> utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
# astimezone()将转换时区为北京时间:
>>> bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
⚪ time:返回时间
time.time()返回自1970年1月1日至今的时间。
import time
start_time = time.time()
end_time = time.time()
print(end_time-start_time)
⚪ timeit:统计运行时间
timeit库可以测试一段代码的运行速度。
class timeit.Timer(stmt = 'pass', setup = '', timer = <timer function>)
Timer是测量一段代码执行速度的类。stmt参数(statement)是要测试的代码语句;setup参数是运行代码需要的设置;timer参数是与平台有关的定时器函数。
timeit.Timer.timeit(number = 1000000)
timeit是Timer类中测试语句执行速度的对象方法。number参数是测试代码的测试次数,默认为1000000。该方法返回执行代码的float类型平均时间。
from timeit import Timer
t = Timer('test()', 'from __main__ import test') # __main__指代本程序
print(t.timeit(number = 1000))
⚪ bisect:查找和插入
bisect库是python的一个查找和插入模块,他接收一个排序后的数组和一个数字,用来按照排序顺序查找或插入该数字。使用这个模块的函数前先确保操作的列表是已排序的:
import bisect
data = [2,7,9]
insort函数执行插入,其插入的结果不会影响原有的排序;insort_left和insort_right函数用于处理将会插入重复数值的情况:
bisect.insort(data, 4)
data = [2,4,7,9]
bisect.insort_left(data, 4)
data = [2,4,4,7,9]
bisect函数查找该数值将会插入的位置并返回,而不会插入;bisect_left和bisect_right函数用于处理将会插入重复数值的情况,返回将会插入的位置:
data = [2,7,9]
bisect.bisect(data, 4)
return 1
data = [2,4,4,7,9]
bisect.bisect_right(data, 4)
return 3
bisect.bisect和bisect.bisect_right返回大于目标值的第一个下标(相当于C++中的upper_bound),bisect.bisect_left返回大于等于目标值的第一个下标(相当于C++中的lower_bound)。
通过bisect_left可以实现二分查找。
idx = bisect.bisect_left(alist, target)
if idx < len(alist) and alist[idx] == target:
return True
return False
⚪ argparse:解析命令行参数
在命令行程序中,经常需要获取命令行参数。为了简化参数解析,可以使用内置的argparse库,定义好各个参数类型后,它能直接返回有效的参数。
import argparse
# 定义一个ArgumentParser实例:
parser = argparse.ArgumentParser(
prog='main', # 程序名
description='Backup MySQL database.', # 描述
epilog='Copyright(r), 2023' # 说明信息
)
# 定义参数,可指定默认值、数据类型、参数注释
parser.add_argument('--port', default='3306', type=int, help='Caption.')
# 允许用户输入简写,可指定必须传入的参数
parser.add_argument('-u', '--user', required=True)
# gz参数不跟参数值,因此指定action='store_true',意思是出现-gz表示True:
parser.add_argument('-gz', '--gzcompress', action='store_true', required=False, help='Compress backup files by gz.')
# 解析参数:
args = parser.parse_args()
print(f'user = {args.user}')
在命令行执行命令python main.py -u zheng即可读取命令行参数。在命令行如果输入-h,则打印帮助信息:
python ./main.py -h
parse_args()非常方便的一点在于,如果参数有问题,则它打印出错误信息后,结束进程;如果参数是-h,则它打印帮助信息后,结束进程。只有当参数全部有效时,才会返回一个NameSpace对象,获取对应的参数就把参数名当作属性获取。
⚪ itertools:迭代器
Python的内建模块itertools提供了非常有用的用于操作迭代对象的函数。itertools提供的几个“无限”迭代器,它们的返回值不是list,而是Iterator,只有用for循环迭代的时候才真正计算:
>>> import itertools
# count()会创建一个无限的迭代器,若直接for循环打印只能按Ctrl+C退出
>>> natuals = itertools.count(1, 2) # 第二个参数为步长
# cycle()会把传入的一个序列无限重复下去
>>> cs = itertools.cycle([1, -1]) # 可以用next(cs)循环地构造正负1
# repeat()负责把一个元素无限重复下去,如果提供第二个参数就可以限定重复次数
>>> ns = itertools.repeat('A', 3)
无限序列只有在for迭代时才会无限地迭代下去,如果只是创建了一个迭代对象,它不会事先把无限个元素生成出来,事实上也不可能在内存中创建无限多个元素。无限序列虽然可以无限迭代下去,但是通常会通过takewhile()等函数根据条件判断来截取出一个有限的序列:
>>> natuals = itertools.count(1)
>>> ns = itertools.takewhile(lambda x: x <= 10, natuals)
>>> list(ns)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
itertools提供的几个迭代器操作函数更加有用:
# chain()可以把一组迭代对象串联起来,形成一个更大的迭代器
>>> for c in itertools.chain('ABC', 'XYZ'):
... print(c)
# 迭代效果:'A' 'B' 'C' 'X' 'Y' 'Z' ...
# groupby()把迭代器中相邻的重复元素挑出来放在一起:
>>> for key, group in itertools.groupby('AAABBB'):
... print(key, list(group))
...
A ['A', 'A', 'A']
B ['B', 'B', 'B']
# groupby()的挑选规则是通过函数完成的,只要作用于函数的两个元素返回的值相等,
# 这两个元素就被认为是在一组的,而函数返回值作为组的key。
# 例如忽略大小写分组,就可以让元素'A'和'a'都返回相同的key:
>>> for key, group in itertools.groupby('AaaBBb', lambda c: c.upper()):
... print(key, list(group))
...
A ['A', 'a', 'a']
B ['B', 'B', 'b']
itertools库可以实现排列组合:
itertools.permutations(alist)
itertools.combinations(alist,r)