第10章 开箱即用

10.1 模块

1. 编写模块

任何Python程序都可作为模块导入。例如编写了下面的程序并将其保存在文件hello.py中,这个文件的名称(不包括扩展名.py)将成为模块的名称。

#hello.py
print("Hello, world!")

2. 让模块可用

两种可用的方式:1)将模块放在正确的位置;2)告诉解释器到哪里去找;

1)将模块放在正确的位置

sys.path包含一个目录(表示为字符串)列表,解释器将在这些目录中查找模块。

>>> import sys, pprint
>>> pprint.pprint(sys.path)
['C:\\Python35\\Lib\\idlelib',
'C:\\Python35',
'C:\\Python35\\DLLs',
'C:\\Python35\\lib',
'C:\\Python35\\lib\\plat-win',
'C:\\Python35\\lib\\lib-tk',
'C:\\Python35\\lib\\site-packages']

如果要打印的数据结构太大,一行容纳不下,可使用模块pprint中的函数pprint(而不是普通print语句)。 pprint是个卓越的打印函数,能够更妥善地打印输出。
可以将自己编写的模块放到上面打印输出的任何一个目录下,但最好是site-packages目录,因为它就是用来放置模块的。

这种实现方式存在的问题:1)Python解释器的目录中充斥着自己编写的模块;2)没有必要的权限将文件保存到Python解释器的目录中。

2)告诉解释器到哪里去找

i)直接修改sys.path(不常见)

(假设模块文件存储在目录C:\python(windows)或~/python(UNIX/macOS)中)

windows:

import sys
sys.path.append('C:/python')

 UNIX/macOS:

不能直接将'~/python'添加到sys.path的末尾,必须使用完整的路径(例如'/home/yourusername/python')或自动创建完整的路径(sys.path.expanduser('~/python'))

import sys
sys.path.append('/home/yourusername/python')
import sys
sys.path.append(sys.path.expanduser('~/python'))

ii)将模块所在的目录添加到环境变量PYTHONPATH(标准做法)

除使用环境变量PYTHONPATH外,还可使用路径配置文件。这些文件的扩展名为.pth,位于一些特殊目录中,包含要添加到sys.path中的目录。有关这方面的详细信息,请参阅有关模块site的标准库文档。

3. 导入模块

>>>import hello
Hello, world!

 当你导入模块时,可能发现其所在目录中除源代码文件外,还新建了一个名为__pycache__的子目录(在较旧的Python版本中,是扩展名为.pyc的文件)。这个目录包含处理后的文件, Python能够更高效地处理它们。以后再导入这个模块时,如果.py文件未发生变化,Python将导入处理后的文件,否则将重新生成处理后的文件。删除目录__pycache__不会有任何害处,因为必要时会重新创建它。

在上例中可以看出,导入hello模块时,执行了其中的代码。但如果再次导入,将什么事也不会发生

>>>import hello
>>>

如果一定要重新加载模块,可使用模块importlib中的函数reload,它接受一个参数(要重新加载的模块),并返回重新加载的模块。如果在程序运行时修改了模块,并希望这种修改反映到程序中,这将很有用。

>>>import importlib
>>>hello = importlib.reload(hello)
Hello, world!

通过实例化模块bar中的类Foo创建对象x后,如果重新加载模块bar,并不会重新创建x指向的对象,即x依然是(来自旧版bar的)旧版Foo的对象。要让x指向基于重新加载的模块中的Foo创建的对象,需要重新创建它。
4. 在模块中添加测试代码

#hello.py
def hello():
    print("Hello, world!")

#一个测试
hello()

将本模块作为普通程序运行时运行正常,但如果在其他程序中将其作为模块导入,也将执行测试代码(即输出“Hello, world!”),这并不是我们所想要的。

因此,我们可以借助__name__(标识模块名称的系统变量,主模块为“__main__”)

#hello.py
def hello():
    print("Hello, world!")

def test():
    hello()

if __name__ == '__main__': test()

当我们直接执行本模块时,本模块(hello模块)就是主模块(__main__),将执行语句test();

当我们导入该模块时,例如在Python Console中>>>import hello,则并不会执行语句test(),因为本模块(hello模块)的__name__属性值为hello,这时的主模块是命令行程序(即Python Console)

5. 包(package)

为组织模块,可将其编组为包(package)。包其实就是另一种模块,但它可包含其他模块。模块存储在扩展名为.py的文件中,而包则是一个目录。要被Python视为包,目录必须包含文件__init__.py。

如果像普通模块一样导入包,文件__init__.py的内容就将是包的内容。例如,如果有一个名为constants的包,而文件constants/__init__.py包含语句PI = 3.14,就可以像下面这样做:

import constants
print(constants.PI)

要将模块加入包中,只需将模块文件放在包目录中即可。还可以在包中嵌套其他包。例如,要创建一个名为drawing的包,其中包含模块shapes和colors,需要创建如表所示的文件和目录(UNIX路径名)。

 导入包:

import drawing

该种导入语句仅可以使用文件__init__.py中的内容,不能使用shapes和colors的内容。

import drawing.colors

该种导入语句可以通过全限定名drawing.colors使用模块colors

from drawing import shapes

该种导入语句可以通过简化名shapes使用模块shapes


10.2 探索模块

1. 模块中包含什么(以模块copy为例)

>>>import copy
>>>

没有引发异常,说明确实有这样的模块。

使用函数dir列出对象的所有属性(对于模块,他列出所有的函数、类、变量等)

>>>dir(copy)
['Error', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_copy_dispatch', '_copy_immutable', '_deepcopy_atomic', '_deepcopy_dict', '_deepcopy_dispatch', '_deepcopy_list', '_deepcopy_method', '_deepcopy_tuple', '_keep_alive', '_reconstruct', 'copy', 'deepcopy', 'dispatch_table', 'error']

可以过滤掉以下划线打头的属性(因为按照约定,这些属性并非供外部使用)

>>>[n for n in dir(copy) if not n.startswith('_')]
['Error', 'copy', 'deepcopy', 'dispatch_table', 'error']

变量__all__:旨在定义模块的公有接口

>>>copy.__all__
['Error', 'copy', 'deepcopy']

其在copy.py文件中的定义为:

__all__ = ["Error", "copy", "deepcopy"]

这意味着如果使用下面导入模块的语句,将只能得到变量__all__中列出的3个函数

from copy import *

若要使用属性PyStringMap,则必须显式地:导入copy并使用copy.PyStringMap;或者使用from copy import PyStringMap 

在编写模块时,像这样设置__all__很有用。因为模块可能包含大量其他程序不需要的变量、函数和类,使用from copy import *可以将它们过滤掉。


10.3标准库:一些深受欢迎的模块

1. sys模块(与Python解释器紧密相关的变量和函数)

函数/变量描述
argv传递给Python解释器的参数,包括脚本名(第0个参数)
exit([arg])退出当前程序;可提供整数指出程序是否成功(默认0);可提供字符串并其作为错误消息,这样程序退出时将显示指定的错误消息以及一个表示失败的编码。
modules一个字典,将模块名映射到加载的模块
path一个列表,包含要在其中查找模块的目录的名称
platform一个平台标识符,如sunos5或win32
stdin标准输入流——一个类似于文件的对象
stdout标准输出流——一个类似于文件的对象
stderr标准错误流——一个类似于文件的对象

例:

#reverseargs.py
import sys
args = sys.argv[1:]
args.reverse()
print(' '.join(args))

注:args为sys.argv的一个副本(不包含第一个参数即脚本名);reverse函数对args参数进行原地翻转,然后以print(' '.join(args))输出args中的参数。

#运行
$ python reverseargs.py this is a test
test a is this

2. os模块

函数/变量描述
environ包含环境变量;例如要访问环境变量PYTHONPATH,可使用表达式os.environ['PYTHONPATH']。该映射也可用于修改环境变量,但并非所有的平台都支持这样做。
system(command)用于运行外部程序(退出Python解释器,并将控制权交给被执行的程序);execv和popen函数也可用于运行外部程序(创建一个到程序的连接)
sep用于路径名中的分隔符;UNIX中的/   Windows中的\\(这种Python语法表示单个反斜杠)   旧式MACOS的:   (在某些平台中,os.altsep包含替代路径分隔符,如Windows中的/)
pathsep用于分隔不同的路径名   UNIX/macOS中的:   Windows中的;
linesep用于文本文件中的行分隔符   UNIX/OS X中的单个换行符(\n)   Windows中的回车和换行符(\r\n)
urandom(n)随系统而异的“真正”(至少是强加密)随机源(如果平台没有提供这个随机源,将引发NotImplementedError异常)

例:在程序中打开web浏览器

UNIX:os.system('/usr/bin/firefox')   当前Python程序将等待命令os.system结束;

Windows:os.system(r'C:\"Program Files (x86)"\"Mozilla Firefox"\firefox.exe')   启动外部程序后,当前Python程序将继续运行;   使用了双引号将Program Files和Mozilla Firefox括起来是因为如果不这样做底层shell将受阻于空白处(对于PYTHONPATH中的路径,也必须这样做)   如果在IDLE中执行这个命令,将出现一个dos窗口,关闭这个窗口后浏览器才会启动。
os.startfile(r'C:\Program Files (x86)\Moszilla Firefox\firefox.exe')   启动外部程序后,当前Python程序将继续运行;   接受一个普通路径(无需使用双引号)

更佳的解决方案:
import webbrowser

webbrowser.open('http://www.python.org')

3. fileinput模块

函数描述
input([files[, inplace[, backup]]])帮助迭代多个输入流中的行;files参数以序列方式指定迭代的文件名,inplace参数指定是否就地处理(即修改原文件)默认为False,backup参数用于给原始文件创建的备份文件指定拓展名。
filename()返回当前文件(即当前处理的行所属文件)的文件名
lineno()返回(累计的)当前行号。处理完一个文件并接着处理下一个文件时,不会重置行号。
filelineno()返回在当前文件中的行号。处理完一个文件并接着处理下一个文件时,将重置这个行号并从1重新开始。
isfirstline()检查当前行是否是文件中的第一行
isstdin()检查最后一行是否来自sys.stdin
nextfile()关闭当前文件并移到下一个文件,且计数时忽略跳过的行。
close()关闭序列

例:代码 

# numberlines.py

import fileinput

for line in fileinput.input(inplace=True):
    line = line.rstrip()
    num = fileinput.lineno()
    print('{:<50} # {:2d}'.format(line, num))

执行 第一个numberlines.py为执行这个python文件,第二个numberlines.py为给执行的python文件提供一个参数

$ python numberlines.py numberlines.py

执行结果

# numberlines.py                                  # 1
                                                  # 2
import fileinput                                  # 3
                                                  # 4
for line in fileinput.input(inplace=True):        # 5
    line = line.rstrip()                          # 6
    num = fileinput.lineno()                      # 7
    print('{:<50} # {:2d}'.format(line, num))     # 8

4. 集合

很久以前,由模块sets中的Set类实现,所以在既有代码中可能遇到Set实例。

在较新的版本中,集合是有内置类set实现,无需导入模块sets。

#可使用序列(或其他可迭代对象)来创建集合,
>>>set(range(10))
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
#也可使用花括号显式地指定,
>>>{0, 1, 2, 3}
{0, 1, 2, 3}
#不能仅使用花括号来创建空集合,因为这将创建一个空字典,
>>>type({})
<class 'dict'>
#集合将忽略重复的元素,
>>>{0, 1, 2, 3, 0, 1, 2, 3, 4, 5}
{0, 1, 2, 3, 4, 5}
#集合中元素的排列顺序是不确定的。
{'fee', 'fie', 'foe'}
{'fie', 'foe', 'fee'}
>>>a = {1, 2, 3}
>>>b = {2, 3, 4}

>>>a.union(b)
{1, 2, 3, 4}
>>>a | b
{1, 2, 3, 4}

>>>a.intersection(b)
{2, 3}
>>>a & b
{2, 3}

>>>a.difference(b)
{1}
>>>a - b
{1}

>>>a.symmetric_difference(b)
{1, 4}
>>>a ^ b
{1, 4}

>>>a.copy()
{1, 2, 3}
>>>a.copy() is a
False

>>>c = a & b
>>>c.issubset(a)
True
>>>c <= a
True

>>>c.issuperset(a)
False
>>>c >= a
False

集合是可变的,因此不能用作字典中的键;集合只能包含不可变(可散列)的值,因此不能包含其他集合。
但是在现实世界中可能必须要将集合作为字典中的键或作为另一个集合的元素,这是可以使用frozenset类型,它表示不可变(可散列)的集合。

>>>a = set()
>>>b = set()
>>>a.add(b)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: unhashable type: 'set'
>>>a.add(frozenset(b))

构造函数frozenset创建给定集合的副本。

5. 堆(heapq模块实现的是小顶堆

堆是一种优先队列,能够以任意顺序添加对象并随时(可能是在两次添加对象之间)找出(并删除)最小的元素(小顶堆)。相比于列表方法min,这样做的效率要高很多。

Python没有独立的堆类型,而只有一个包含一些堆操作函数的模块,模块名为heapq(其中的q表示队列),它包含6个函数,其中前4个与堆操作直接相关。必须使用列表来表示堆对象本身。

函数描述
heappush(heap, x)将x压入堆中(不要应用于普通列表,只能应用于各种堆函数创建的列表,即具备堆特征的列表)
heappop(heap)从堆中弹出最小的元素
heapify(heap)让列表具备堆特征(堆特征(以小顶堆为例):位置i处的元素总是大于位置i//2处的元素,反过来说就是位置i处的元素小于位置2 * i和2 * i + 1处的元素)

heapreplace(heap, x)

弹出最小的元素,并将x压入堆中;相当于先heappop(heap)再heappush(heap, x),但效率比前面更高
nlargest(n, iter)返回可迭代对象iter中n个最大的元素(可通过先排序再切片来完成,但堆算法速度更快使用内存更少)
nsmallest(n, iter)返回可迭代对象iter中n个最小的元素(可通过先排序再切片来完成,但堆算法速度更快使用内存更少)

使用heappush从空列表开始创建堆:

>>>from heapq import *
>>>from random import shuffle
>>>data = list(range(10))
>>>shuffle(data)
>>>heap = []
>>>for n in data:
...    heappush(heap, n)
...    
>>>heap
[0, 2, 1, 3, 4, 6, 7, 5, 8, 9]
>>>heappush(heap, 0.5)
>>>heap
[0, 0.5, 1, 3, 2, 6, 7, 5, 8, 9, 4]

使用heapify函数使普通列表具备堆特征:

>>>heap = [5, 8, 0, 3, 6, 7, 9, 1, 4, 2]
>>>heapify(heap)
>>>heap
[0, 1, 5, 3, 2, 7, 9, 8, 4, 6]

6. 双端队列(类型deque,在模块collections中;与集合(set)一样,双端队列也是从可迭代对象创建的)

>>>from collections import deque
>>>q = deque(range(5))
#右端压入
>>>q.append(5)
#左端压入
>>>q.appendleft(6)
>>>q
deque([6, 0, 1, 2, 3, 4, 5])

#右端弹出
>>>q.pop()
5
#左端弹出
>>>q.popleft()
6
>>>q
deque([0, 1, 2, 3, 4])

#元素向右移动3格,并在到达一端时环绕到另一端
>>>q.rotate(3)
>>>q
deque([2, 3, 4, 0, 1])
#元素向左移动1格,并在到达一端时环绕到另一端
>>>q.rotate(-1)
>>>q
deque([3, 4, 0, 1, 2])

7. time模块(Python中还有两个较新的与时间相关的模块:datatime(提供了日期和时间算术支持);timeit(可帮助计算代码段的执行时间)

Python日期元组中的字段
索引字段
0如2000、2001等
1范围1-12
2范围1-31
3范围0-23
4范围0-59
5范围0-61(考虑到闰一秒和闰两秒的情况)
6星期范围0-6,其中0表示星期一
7儒略日范围1-366
8夏令时0、1或-1(布尔值True/False;如果使用-1,那么mktime[将时间元组转换为时间戳(从新纪元开始后的秒数)的函数]可能得到正确的值)

例如:(2008, 1, 21, 12, 2, 56, 0, 21, 0)表示2008年1月21日12时2分56秒,这一天是星期一,2008年的第21天(不考虑夏令时)

注:夏令时,表示为了节约能源,人为规定时间的意思,详见百度百科

模块time中一些重要的函数
函数描述
asctime([tuple])将当前时间转换为字符串
localtime([secs])将时间戳转换为日期元组(本地时间);如想要转换为国际标准时间,应使用gmtime
mktime(tuple)将日期元组转换为时间戳,与localtime功能相反
sleep(secs)让解释器等待指定的秒数
strptime(string[, format])
将一个字符串(其格式与 asctime所返回字符串的格式相同)转换为日期元组。
time()返回时间戳

注:函数time.asctime将当前时间转换为字符串,如下所示:
>>> time.asctime()
'Mon Jul 18 14:06:07 2016'
如果不想使用当前时间,也可向它提供一个日期元组(如localtime创建的日期元组)。要设置更复杂的格式,可使用函数strftime

8. random模块

函数描述
random()返回一个[0, 1)的随机实数
getrandbits(n)以长整数的方式返回n个随机的二进制位
uniform(a, b)返回一个[a, b)的随机实数
randrange([start], stop, [step])返回一个 [start, stop)且步长为step 的随机整数,start默认0,step默认1;例如生成一个1~10的随机整数可用randrange(1, 11)或randrange(10);生成一个小于20的随机正奇数可用randrange(1, 20, 2)
choice(seq)从给定序列中随机(均匀)的选择一个元素
shuffle(seq[, random])就地打乱序列seq
sample(seq, n)从序列seq中随机地选择n个值不同的元素

9. shelve模块

将数据存储到文件中的一种简单的存储方案,使用open打开,可以像普通字典那样操作它(但是键必须是字符串),操作完毕使用close关闭并将所做的修改存盘(close前所做的修改只是在内存中的修改而并没有真正保存到磁盘文件中)。

>>>import shelve
>>>s = shelve.open('test.bat')
>>>s['x'] = ['a', 'b', 'c']
>>>s['x']
['a', 'b', 'c']
>>>s.close()

s['x']返回的是一个副本,对s['x']的append操作不是原址操作(且该操作不返回任何值),不能改变s['x']的值;
解决方案是将s['x']赋予一个临时变量,然后对该变量进行append操作后再重新赋予s['x'],从而达到修改s['x']值的目的;
因为s['x'].append('d')不返回任何值,所以不能写为s['x'] = s['x'].append('d')

>>>s['x']
['a', 'b', 'c']
>>>s['x'].append('d')
>>>s['x']
['a', 'b', 'c']
>>>
>>>temp = s['x']
>>>temp.append('d')
>>>s['x'] = temp
>>>s['x']
['a', 'b', 'c', 'd']

关于writeback参数:默认为False;当设置为True以后,shelf将会将所有从DB(此处即磁盘文件)中读取的对象存放到一个内存缓存,当我们close()关闭打开的shelf的时候,缓存中所有的对象会被重新写入DB。
优点:减少出错概率,并且让对象的持久化对用户更加的透明;
缺点:增加额外的内存消耗;当DB在close()的时候会将缓存中的每一个对象都写入到DB,会带来额外的等待时间(因为shelve没有办法知道缓存中哪些对象修改了,哪些对象没有修改,因此所有的对象都会被写入)。

例子10-8:

# database.py
import sys, shelve

def store_person(db):
    """
    Query user for data and store it in the shelf object
    """
    pid = input('Enter unique ID number: ')
    person = {}
    person['name'] = input('Enter name: ')
    person['age'] = input('Enter age: ')
    person['phone'] = input('Enter phone number: ')
    db[pid] = person

def lookup_person(db):
    """
    Query user for ID and desired field, and fetch the corresponding data from
    the shelf object
    """
    pid = input('Enter ID number: ')
    field = input('What would you like to know? (name, age, phone) ')
    field = field.strip().lower()

    print(field.capitalize() + ':', db[pid][field])

def print_help():
    print('The available commands are:')
    print('store  : Stores information about a person')
    print('lookup : Looks up a person from ID number')
    print('quit   : Save changes and exit')
    print('?      : Prints this message')

def enter_command():
    cmd = input('Enter command (? for help): ')
    cmd = cmd.strip().lower()
    return cmd

def main():
    database = shelve.open('C:\\database.dat') # You may want to change this name
    try:
        while True:
            cmd = enter_command()
            if  cmd == 'store':
                store_person(database)
            elif cmd == 'lookup':
                lookup_person(database)
            elif cmd == '?':
                print_help()
            elif cmd == 'quit':
                return
    finally:
        database.close()

if name == '__main__': main()

运行: 

Enter command (? for help): ?
The available commands are:
store  : Stores information about a person
lookup : Looks up a person from ID number
quit   : Save changes and exit
?      : Prints this message
Enter command (? for help): store
Enter unique ID number: 001
Enter name: Mr. Gumby
Enter age: 42
Enter phone number: 555-1234
Enter command (? for help): lookup
Enter ID number: 001
What would you like to know? (name, age, phone) phone
Phone: 555-1234
Enter command (? for help): quit

注:capitalize()将字符串的第一个字母变成大写,其他字母变小写。

10. re模块

1)正则表达式

普通字符:只与自己匹配,作为字面字符;
通配符(.):与除换行符外的任何单个字符都匹配;
对特殊字符(.*?等)进行转义:例如要匹配字符串中'python.org',因为.是特殊字符(通配符),所以要对其进行转义,表达式应该为'python\\.org'(这里包括两层转义:解释器执行的转义和模块re执行的转义,因为在字符串中\为转义字符,所以\\才表示一个真正的\),也可以使用原始字符串r'python\.org'
字符集:匹配范围内的单个字符,'[pj]ython'与'python'和'jython'都匹配,[a-z]与a~z的任何字符都匹配,[a~zA~Z0~9]与大写字母,小写字母和数字都匹配。
排除字符集:[^abc]与除a、b、c外的其他任何字符都匹配。
特殊字符(.*?等)作字面字符而不是正则表达式运算符时必须使用反斜杆对其进行转义;在字符集中,通常无需对这些字符(.*?等)进行转义,当然进行转义也是完全合法的。需牢记:
·脱字符(^)位于字符集开头时,除非要将其用作排除运算符,否则必须对其进行转义;
·同样,右方括号(])和连字符(-)要么将其放在字符集开头,要么使用反斜杆对其进行转义;

二选一(|):'python|perl'匹配'python'和'perl'
子模式: 有时候想将运算符(例如二选一运算符)应用于模式的一部分而不是整个模式,可将这部分放在圆括号内,这部分即称为子模式单个字符也可称为子模式),例如'p(ython|erl)'
可选模式(?):放在子模式后面,将其指定为可选的。例如r'(http://)?(www\.)?python\.org'与下面的这些字符串匹配:
'http://www.python.org'
'http://python.org'
'www.python.org'
'python.org'
重复模式(*+):
(pattern)*:pattern可重复0、1或多次。
(pattern)+:pattern可重复1或多次。
(pattern){m, n}:模式可重复m~n次。
字符串开头(^即脱字符)和结尾($):例如'^http'与http开头的字符串匹配,'org$'与org结尾的字符串匹配。

2)模块re的内容

compile(pattern[, flags]):根据包含正则表达式的字符串创建模式对象
使用时为re.compile

search(pattern, string[, flags]):在给定字符串中查找第一个与指定正则表达式匹配的子串,找到返回MatchObject(结果为真),否则返回None(结果为假),例如:

if re.search(pat, string):
    print('Found it!')

re.search(pat, string)(此处的pat是字符串)等效于pat.search(string)(此处的pat是使用compile创建的模式对象),下面的函数类似。

match(pattern, string[, flags]):在模式与字符串开头匹配时即返回MatchObject(如要求匹配整个字符串,需要在模式末尾加上一个美元符$)

split(pattern, string[, maxsplit=0]):根据与模式匹配的子串来分割字符串(与字符串方法split不同的是其可以使用正则表示式例如'[, ]+'(即任意个逗号或空格组成的字符串),而字符串方法只能使用固定的分隔符);如果模式中包含圆括号,将在分割得到的子串中间插入括号中的内容;maxsplit指定最多分割次数;

>>>some_text = 'alpha, beta,,,,gamma     delta'
>>>re.split('[, ]+', some_text)
['alpha', 'beta', 'gamma', 'delta']
>>>re.split('[, ]+', some_text, maxsplit = 2)
['alpha', 'beta', 'gamma     delta']
>>>re.split('[, ]+', some_text, maxsplit = 1)
['alpha', 'beta,,,,gamma     delta']

此处可以看到以o为分割符时,中间元素为空字符,推测split的分割方式为找到第一个o,将原字符串分成了左边的'f'和右边的'obar',再找到第2个'o',分为左边的''和又不的'bar',递归进行。 

>>>re.split('o', 'foobar')
['f', '', 'bar']
>>>re.split('oo', 'foobar')
['f', 'bar']
>>>re.split('o(o)', 'foobar')
['f', 'o', 'bar']

findall(pattern, string)返回一个列表,其中包含所有与给定模式匹配的子串。例如要找出字符串包含的所有单词:

>>>pat = '[a-zA-Z]+'
>>>text = '"Hm... Err -- are you sure?" he said, sounding insecure.'
>>>re.findall(pat, text)
['Hm', 'Err', 'are', 'you', 'sure', 'he', 'said', 'sounding', 'insecure']

要查找所有的标点符号,可以写作:(这里对-进行了转义,因此Python就不会认为它是用来指定字符范围的(例如a-z))

>>>pat = r'[.?\-",]+'
>>>re.findall(pat, text)
['"', '...', '--', '?"', ',', '.']

sub(pat, repl, string[, count=0])从左往右将与模式匹配的子串替换为指定内容:

>>>pat = '{name}'
>>>text = 'Dear {name}...'
>>>re.sub(pat, 'Mr. Gumby', text)
'Dear Mr. Gumby...'

escape(string)对字符串中所有的正则表达式特殊字符都进行转义。使用该函数的情况有:1)字符串很长且包含大量的特殊字符,而又不想输入大量的反斜杠;2)从用户那里获得了一个字符串(例如,通过函数input),想将其用于正则表达式中;工作原理:

>>>re.escape('www.python.org')
'www\\.python\\.org'
>>>re.escape('But where is the ambiguity?')
'But\\ where\\ is\\ the\\ ambiguity\\?'

3)匹配对象和编组

匹配对象即之前的MatchObject对象,该对象中包含与模式匹配的子串的信息,还包含模式的哪部分与子串的哪部分匹配的信息;
编组是放在括号内的子模式;
用例子说明,看以下模式:

'There (was a (wee) (cooper)) who (lived in Fyfe)'

包含如下编组:
0 There was a wee cooper who lived in Fyfe
1 was a wee cooper
2 wee
3 cooper
4 lived in Fyfe

re匹配对象的重要方法
方法描述
group([group1, ...])获取与给定子模式(编组)匹配的子串,默认为0,可设置多个参数则返回一个结果元组
start([group])返回与给定编组(默认0)匹配的子串的起始位置
end([group])返回与给定编组(默认0)匹配的子串的终止位置(与切片一样,不包含终止位置)
span([group])返回与给定编组匹配的子串的起始和终止位置

对于模式r'www\.(.+)\.com$',返回的MatchObject对象的编组0是与整个模式相匹配的子串,而编组1是前面子串的'www.'和'.com'之间的内容;

>>>m = re.match(r'www\.(.*)\..{3}', 'www.python.org')
>>>m.group(1)
'python'
>>>m.start(1)
4
>>>m.end(1)
10
>>>m.span(1)
(4, 10)

4)替换中的编组和函数(即re.sub使用拓展)

例子说明:假设'*something*'是在纯文本文档(如电子邮件)中表示突出的普通方式,'<em>something</em>是相应的HTML代码(用于网页中),欲将前者替换为后者。

>>>emphasis_pattern = r'\*([^\*]+)\*'

为了在正则表达式中更好的添加注释以便于理解,可以使用VERBOSE标志,它使得我们能够在正则表达式中添加空白(空格、制表符、换行符等)而re将忽略它们——除非将它放在字符类中或使用反斜杆对其进行转义。如下:

>>> emphasis_pattern = re.compile(r'''
... \*                    #起始突出标志——一个星号
... (                     #与要突出的内容匹配的编组的起始位置
... [^\*]+                #与除星号外的其他符号都匹配
... )                     #编组到此结束
... \*                    #结束突出标志
...               ''', re.VERBOSE)
...
>>>re.sub(emphasis_pattern, r'<em>\1</em>', 'Hello, *world*!')
'Hello, <em>world</em>!'

解释:在'Hello, *world*!'中匹配emphasis_pattern匹配到'*world*',其中编组1的内容为'world',将''*world*'替换为r'<em>\1</em>'即替换为'<em>world</em>'

贪婪模式和非贪婪模式:

重复运算符是贪婪的,这意味着它将匹配尽可能多的内容,例如

>>>emphasis_pattern = r'\*(.+)\*'
>>>re.sub(emphasis_pattern, r'<em>\1</em>', '*This* is *it*!')
'<em>This* is *it</em>!'

可以发现,结果不是'<em>This</em> is <em>it</em>!',即+是贪婪的,emphasis_phattern匹配到了整个串。

>>>emphasis_pattern = r'\*([^\*]+)\*'
>>>re.sub(emphasis_pattern, r'<em>\1</em>', '*This* is *it*!')
'<em>This</em> is <em>it</em>!'

该写法结果为'<em>This</em> is <em>it</em>!',这是因为[^\*]不能匹配'*'(即使用排除运算符)。但如果突出方式为两个星**,则这种阻止贪婪的方法将不能使用,因此可以使用非贪婪的重复运算符+?

>>>emphasis_pattern = r'\*\*(.+?)\*\*'
>>>re.sub(emphasis_pattern, r'<em>\1</em>', '**This** is **it**!')
'<em>This</em> is <em>it</em>!'

#对比贪婪的重复运算符+
>>>emphasis_pattern = r'\*\*(.+)\*\*'
>>>re.sub(emphasis_pattern, r'<em>\1</em>', '**This** is **it**!')
'<em>This** is **it</em>!'

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MallocLu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值