Python文本处理(二)difflib & textwrap 模块

difflib模块用来辅助计算文本差异,difflib模块提供比较序列的类和函数,可以用来进行序列或文件的比较并产生不同格式的信息,包括HTML和上下文以及统一格式的差异。

difflib文本比较类

​ difflib提供了用来文本比较的类,有以下内容

SequenceMatcher

SequenceMatcher:序列分析器,可以用于比较任何序列元素是可哈希的序列对,该类比较灵活。所采用的基本算法是在20世纪80年代后期由Ratcliff和Obsershelp提出来的,该基本算法的另一种叫法是”格式塔模式匹配算法”,算法的思想是找到最长的连续匹配子序列,子序列不包含无意义的空行或空字符,之后将该思想应用到递归匹配序列左侧和右侧序列片段。

时间控制&自动标识丢弃垃圾字符串

​ 在时间复杂度上,SequenceMatcher用来做比较时的最坏算法时间复杂度是$O(n^2)$, 最佳情况的时间复杂度是线性的$O(n)$,其预期情况取决于由多少个元素以及元素之间的复杂程度。

垃圾自动标识和触发丢弃功能也是SequenceMatcher的另一项重要功能。SequenceMatcher支持自动将某些序列视为垃圾并自动丢弃。垃圾自动标识是计算每个项目在序列中出现的次数,如果一个项目的重复项占序列的1%以上,并且序列长度至少为200个项目,则该项目会被标记为”受欢迎”被视为垃圾自动丢弃,以便更好的进行序列匹配。在创建SequenceMatcher的时候,可以通过将autojunk参数设置为false的方式关闭此功能。不过autojunk参数是Python3.2才有的功能。

SequenceMatcher构建

SequenceMatcher的创建函数用模块级别函数difflib.SequenceMatcher(isjunk=None, a=’’, b=’’, autojunk=True),可选参数isjunk必须是none(默认值),或者是一个单参数函数,该函数接受一个序列元素,并且仅当该元素被定义为垃圾字符且应被忽略时才返回true。为isjunk传递none等同于传递lambda x:0;换句话说,不忽略任何元素。如以下lambda函数

lambda x:x in '\t'

以上代表如果将行作为字符序列进行比较,并且在空白或硬制表符上不进行同步比较。

​ 可选参数ab是要比较的序列;都默认为空字符串。两个序列的元素都必须是可哈希的;可选参数autojunk可用于禁用自动垃圾启发式。

SequenceMatcher对象有三个数据属性:当isjunk值为true时,bjunk是b的一组元素;bpoular是垃圾自动启发式控制,如果bpoular没有被禁用,则认为出现频率高的字符并非垃圾元素;b2j是将b的其余元素映射到它们出现的位置列表的dict。当通过set_seqs()set_seq2()重置字符列表b时,这三个参数都会跟着重置。

SequenceMatcher函数

SequenceMatcher对象有以下操作函数:

set_seqs()

set_seqs(a,b)比较两个字符序列。SequenceMatcher计算并缓存有关第二个序列的详细信息,因此,如果要将一个序列与多个序列进行比较,请使用set_seq2()将常用的序列设置一次,并对其他每个序列重复调用set_seq1()

set_seq1()

set_seq1(a) 设置要比较的第一个序列,要比较的第二个序列不发生改变。

set_seq2()

set_seq2(b)设置要比较的第二个序列,要比较的第一个序列不发生改变。

find_longest_match()

find_longest_match(alo, ahi, blo, bhi)在a[alo:ahi]b[blo:bhi]中查找最长的匹配块,并返回一个被命名的元组Match(a, b, size)

​ 如果isjunk被省略或没有,则find longer_match()返回(i,j,k),使a[i:i+k]等于b[j:j+k],其中alo<=i<=i+k<=ahiblo<=j<=j+k<=bhi。对于满足这些条件的所有(i’,j’,k’)而言,如果满足i==i’`,j<=j’,则满足附加条件k>=k’i<=i’。换句话说,在所有最大匹配块中,返回在a中最早开始的匹配块,在a中最早开始的最大匹配块中,返回在`b中最早开始的匹配块。

>>> import difflib
>>> s = difflib.SequenceMatcher(None, 'abcd', 'abcd abcd')
>>> s.find_longest_match(0,4,0,9)
Match(a=0, b=0, size=4)
>>> 

如果提供了isjunk,则首先按照上述方法确定最长的匹配块,但有一个附加限制,即块中不显示任何垃圾元素。然后,通过匹配(仅)两侧的垃圾元素,尽可能扩展该块。因此,结果块永远不会在垃圾上匹配,除非相同的垃圾恰好与一个有趣的匹配相邻。

>>> s = difflib.SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=1, b=0, size=4)

这个举例与上一个举例一样,但是考虑到空白是垃圾。设置isjunk后将阻止“abcd”直接匹配第二个序列末尾的“abcd”。相反,只有“abcd”可以匹配,并且在第二个序列中与最左边的“abcd”匹配,如果没有匹配块,将返回(alo, blo, 0)

get_matching_blocks()

get_matchinig_blocks()返回描述非重叠匹配子序列的三元组列表。每个三元组的形式是(i,j,n),意思是a[i:i+n]==b[j:j+n]ij在三元组中是单调递增的。最后一个三元组是一个虚拟值,数值为(len(a)、len(b)、0),是唯一一个n==0的三元组。如果(i,j,n)和(i’,j’,n’)是列表中的相邻三元,而第二个不是列表中的最后一个三元,则i+n<i’或j+n<j’;换句话说,相邻三元总是描述非相邻的相等块。

>>> s = difflib.SequenceMatcher(None, 'abxcd', 'abcd')
>>> s.get_matching_blocks()
[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
get_opcodes()

get_opcodes()返回描述如何将a转换为b的5个元组的列表。每个元组的形式为(tag,i1,i2,j1,j2)。第一个元组的i1==j1==0,其余的元组的i1等于前一个元组的i2,同样,j1等于前一个j2

tag参数是string形式,其值与含义如下:

含义
'replace' a[i1:i2]b[j1:j2]替换
'delete' a[i1:i2] 将被删除. 在这种情况下j1 == j2
'insert' b[j1:j2] 将插入在 a[i1:i1]的位置. 在这种情况下 i1 == i2
'equal' a[i1:i2] == b[j1:j2] (子序列相等).
>>> a = 'qabxcd'
>>> b = 'abycdf'
>>> s = difflib.SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
    print ('{:7}  a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(tag, i1, i2, j1, j2, a[i1:i2],b[j1:j2]))


delete   a[0:1] --> b[0:0]      'q' --> ''
equal    a[1:3] --> b[0:2]     'ab' --> 'ab'
replace  a[3:4] --> b[2:3]      'x' --> 'y'
equal    a[4:6] --> b[3:5]     'cd' --> 'cd'
insert   a[6:6] --> b[5:6]       '' --> 'f'
get_grouped_opcodes()

get_grouped_opcodes(n=3)返回具有最多n行上下文的组生成器,从get_opcodes()返回的组开始,该方法分离出较小的变更集群,并消除没有变更的中间范围,这些组的返回格式与get_opcodes()相同。

ratio()

ration()返回序列相似性的度量值,范围[0,1]中的浮点值。其中T是两个序列中元素的总数,M是匹配的数目,值是2.0*M/T。如果序列相同,值1.0;如果没有共同点,值0.0。如果还没有调用get_matching_blocks()get_opcodes(),计算过程会比较费力,此时需要先尝试quick_ratio()real_quick_ratio()以获得上界。

quick_ratio()

quick_ratio()相对快速地返回ratio()的上限

real_quick_ratio()

real_quick_ratio()快速返回ratio()的上限,速度比quick_ratio()快

由于近似程度不同,返回匹配与总字符比率的三种方法可以给出不同的结果,尽管quick_ratio()和real_quick_ratio()始终至少与ratio()一样大。

>>> s = difflib.SequenceMatcher(None, 'abcd', 'bcde')
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
>>> 

SequenceMatcher匹配综合运用

>>> #示例匹配两个string,设置空白字符为垃圾字符
>>> s = difflib.SequenceMatcher(lambda x: x ==' ', 'private Thread currentThread;', 'private volatile Thread currentThread;')
>>> print (round(s.ratio(), 3))
0.866
>>> for block in s.get_matching_blocks():
    print ('a[%d] and b[%d] match for %d elements' %block)


a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements
>>> for opcode in s.get_opcodes():
    print('%6s a[%d:%d] b[%d:%d]' % opcode)


 equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
 equal a[8:29] b[17:38]

Differ

​ Differ是基于SequenceMatcher用来比较文本行并生成人工可读的差异性的来。Differ使用SequenceMatcher进行序列比较,进一步比较相似行中的字符序列。但不同生成的增量并不表示差异最小。相反,最小的差异通常是与正常预期相反的,因为它们可以在任何可能的地方同步,有时会意外地匹配100页。将同步点限制为连续匹配保留了局部性的一些概念,偶尔会产生较长的差异。

​ Differ每一行序列比较都是以两个字母的状态码开头,每个状态码都有其固定意义:

状态码 意义
‘- ‘ 一个减号字符和一个空格 序列1的唯一行
‘+ ‘ 一个加号字符和一个空格 序列2的唯一行
‘ ‘ 两个空字符 序列1和序列2的通用行
‘? ‘ 问号和空字符 不支持序列1或序列2任何一种情况的唯一行

notes: 以’?’开头的序列并不支持任何情况,当序列包含tab键值的时候该状态码的意义不存在。

Differ构建

Differ的构建同样采用模块函数,difflib.Differ(linejunk=None, charjunk=None)。可选关键字参数linejunkcharjunk要使用筛选函数或设置为None,linejunk只接收一长串字符串参数,如果字符串是垃圾字符串,则返回True,否则返回False;charjunk接收一个字符参数,如果字符是垃圾字符,返回True,否则返回False。

Differ垃圾过滤功能可以加快匹配速度以发现差异,并且不会导致忽略任何不同的行或字符。

Differ唯一函数compare()

截至到Python3.7版本,Differ类只有唯一的一个调用函数,compare(a,b)比较两个行序列,并生成一个行序列。每个序列必须包含以换行符结尾的单个单行字符串。这样的序列可以从类文件对象的readlines()方法中获得。生成的增量还包括以换行符结尾的字符串,可以通过类似文件的对象的writelines()方法按原样打印。

Differ使用示例

>>> from pprint import pprint
>>> import difflib
>>> import sys
>>> #比较两个本文。首先,设置文本,文本是换行符结尾的单个单行字符串序列(这些序列也可以从类文件对象的readlines()方法中获得)
>>> text1 = '''  1. Beautiful is better than ugly.
...   2. Explicit is better than implicit.
...   3. Simple is better than complex.
...   4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len (text1)
5
>>> text1[0][-1]
'\n'
>>> text2 =  '''  1. Beautiful is better than ugly.
...   3.   Simple is better than complex.
...   4. Complicated is better than complex.
...   5. Flat is better than nested.
... '''.splitlines(keepends=True)
>>> len(text2)
5
>>> #创建Differ类
>>> d = difflib.Differ()
>>> #比较text1和text2
>>> result = list(d.compare(text1, text2))
>>> pprint(result)
['    1. Beautiful is better than ugly.\n',
 '- ...   2. Explicit is better than implicit.\n',
 '- ...   3. Simple is better than complex.\n',
 '+ ...   3.   Simple is better than complex.\n',
 '?         ++\n',
 '- ...   4. Complex is better than complicated.\n',
 '?                ^                     ---- ^\n',
 '+ ...   4. Complicated is better than complex.\n',
 '?               ++++ ^                      ^\n',
 '+ ...   5. Flat is better than nested.\n',
 '  ... ']
>>> sys.stdout.writelines(result)
    1. Beautiful is better than ugly.
- ...   2. Explicit is better than implicit.
- ...   3. Simple is better than complex.
+ ...   3.   Simple is better than complex.
?         ++
- ...   4. Complex is better than complicated.
?                ^                     ---- ^
+ ...   4. Complicated is better than complex.
?               ++++ ^                      ^
+ ...   5. Flat is better than nested.

HtmlDiff

HtmlDiff类用于创建一个HTML表或包含该HTML表的完整文件,来显示文本的并排、逐行比较以及行间的行内更改突出显示。HTML表在完全比较模式或差异模式比较情况下产生。

HtmlDiff类的函数:

init()

 __init__ (tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARCTER_JUNK) 是HtmlDiff类的初始化函数。

tabsize是一个可选的关键字参数,用于指定制表位间距,默认值为8

wrapcolumn是一个可选的关键字参数,用于指定行断开和换行的列号,默认为无,不换行

linejunkcharjunk是传递给ndiff()的可选关键字参数(后面会介绍此方法)。

make_file()

make_file(fromlines, tolines, fromdesc=’’, context=False, numlines=5, , charset=’utf-8’* )函数用来比较fromlinestolines两个字符串列表,并返回一个字符串,该字符串是一个完整的HTML文件,其中包含一个表,显示行与行之间的差异以及突出显示的行内更改。

fromdesc和 `todesc 是可选的关键字参数,用来指定from/to 文件列标题字段,默认值都为空值。

contextnumlines都是可选的关键字参数,当要显示文本内容差异时,设置contextTrue,默认是False 显示完整文件。numlines默认值为5,当context设置为truenumlines控制围绕内容差异显示的行数;当context设置为falsenumlines控制显示当使用“下一个”超链接时文本内容差异行数(设置为零将导致链接内容将不带任何前导内容的next链接显示放置在浏览器顶部)。

​ Python3.5之后添加了可选关键字参数charset, 代表HTML的默认文集,HTML文档的默认字符集从so-8859-1更改为utf-8

make_table()

make_table(fromlines, tolines, fromdesc=’’, todesc=’’, context=False, numline=5)函数用来比较fromlinestolines两个字符串列表并返回一个字符串,该字符串是一个完整的HTML表格,该表格显示行与行之间的差异以及突出显示的行内更改。该方法作用与make_file()函数大致相同,只不过一个返回的是文件,一个是直接返回表格。

difflib文本比较函数

difflib提供了一些文本比较函数,部分函数如下:

context_diff()

context_diff(a, b, fromfile=’’, fromfiledate=’’, tofiledate=’’, n=3, lineterm=’\n’)用来比较a和b两个字符串列表,并返回一个内容差异格式的变量。

​ 内容差异以紧凑的方式展示更改的行、和更改行前后几行内容,以及更改前后的样式显示,显示的内容行数由lineterm参数决定,默认为3。

​ 默认情况下,差异控制行(带有***或—)尾随着新行创建,这有助于使从io.iobase.readlines()创建的输入产生适合与io.iobase.writelines()一起使用的内容,因为输入和输出都有尾随的换行符。对于不具有尾随换行的输入,将lineterm参数设置为’’,以便将输出统一为无换行符。

​ 内容差异格式通常具有文件名和修改时间的标题。可以使用fromfiletofilefromfiledatetofiledate的字符串指定其中的部分值或全部值。修改时间通常以ISO 8601格式表示。如果未指定,字符串默认为空白。

>>> import sys
>>> import difflib
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(difflib.context_diff(s1,s2, fromfile='before.py', tofile='after.py'))
*** before.py
--- after.py
***************
*** 1,4 ****
! bacon
! eggs
! ham
  guido
--- 1,4 ----
! python
! eggy
! hamster
  guido
>>> 

get_close_matches()

get_close_matches(word, possibilities, n=3, cutoff=0.6)返回一个最佳匹配列表。word是一个需要匹配的序列(通常是一个字符串),并且也可能是一个序列列表(通常是一个字符串列表)与word匹配。

​ 可选参数cutoff是范围在[0,1]内的浮点数,代表与word序列匹配被忽略的相似度,假设cutoff设置为0.4,则代表要与word序列匹配的匹配度达到60%。

​ 可选参数n代表要返回的最大匹配数,默认为3,n的取值必须大于0。匹配度最好可能性的匹配(不超过n个)将在一个返回列表中返回, 按匹配度高低排序,最相似的先返回。

>>> import keyword
>>> import difflib
>>> difflib.get_close_matches('applel', ['ape', 'apple', 'peach', 'puppy'])
['apple', 'ape']
>>> difflib.get_close_matches('wheel', keyword.kwlist)
['while']
>>> difflib.get_close_matches('pineapple', keyword.kwlist)
[]
>>> difflib.get_close_matches('accept', keyword.kwlist)
['except']
>>> 

ndiff()

ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)函数比较a和b两个字符串序列,返回不同样式的差异变量(产生差异变量行的生成器)。

​ 可选参数linejunkcharjunk是筛选操作函数:linejunk接收单个字符串参数,如果字符串被定义为垃圾字符,则返回true,否则返回False,默认没有设定值。此外,还有一个模块函数IS——LINK_JUNK(),能过滤掉没有可见字符(除空字符、换行符之类的字符之外的字符)的行。但是SequenceMacther类能够动态分析哪些行 出现频繁以至于造成干扰,通常比使用IS——LINK_JUNK()函数效果更好。

charjunk接收一个长度为1的字符串,如果字符串被定义为垃圾字符,则返回true,否则返回False。默认值是模块函数IS_CHARACTER_JUNK(),它过滤掉空白字符(空白或制表符)。

>>> differstr = difflib.ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),  'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> differstr = list(differstr)
>>> print (' '.join(restore(differstr, 1)), end='')
one
two
three
>>> print(''.join(restore(differstr, 2)), end='')
ore
tree
emu

unified_diff()

unified_diff(a, b, fromfile=’’, tofile=’’, fromfiledate=’’, tofiledate=’’, n=3, lineterm=’\n’)用来比较a和b两个字符串,以统一的个数返回差异变量。unified diffs相较于context diffs是一种简便的显示方式,只显示已更改的行和几行上下文,更改以内联样式显示(而不是单独的前/后块)。修改内容的前后文行数由n设置,默认为3行。

​ 默认情况下,差异控制行(带有***或—)尾随着新行创建,这有助于使从io.iobase.readlines()创建的输入产生适合与io.iobase.writelines()一起使用的内容,因为输入和输出都有尾随的换行符。对于不具有尾随换行的输入,将lineterm参数设置为’’,以便将输出统一为无换行符。内容差异格式通常具有文件名和修改时间的标题。可以使用fromfiletofilefromfiledatetofiledate的字符串指定其中的部分值或全部值。修改时间通常以ISO 8601格式表示。如果未指定,字符串默认为空白。以上情况与context_diff()函数一样。

>>> import sys
>>> import difflib
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(difflib.unified_diff(s1,s2, fromfile='befor.py', tofile='after.py'))
--- befor.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido

diff_bytes()

diff_bytes(dfunc, a, b, fromfile=b’’, tofile=b’’, fromfiledate=b’’, tofiledate=b’’, n=3, lineterm=b’\n’)使用dfunc比较a和b两个字符串列表;以dfunc返回的格式生成一个增量行序列(也是字节),dfunc必须是可调用的,通常是unified_diff()context_diff()

​ 该函数允许比较具有未知或不一致编码的数据。除n之外的所有输入都必须是字节对象,而不是str类型。通过无损地将所有输入(n除外)转换为str并调用dfunc(a、b、fromfile、tofile、fromfiledate、tofiledate、n、lineterm)来工作。然后将dfunc的输出转换回字节,因此接收到的delta行与a和b具有相同的未知(或是不一致)编码,这是Python3.5新出的功能。

IS_LINK_JUNK(line)对于忽略行返回true。如果行为空或包含单个“”,则行是可忽略的,否则它是不可忽略的。被ndiff()用来作为linejunk参数的默认值。

IS_CHARACTER_JUNK()

IS_CHARACTER_JUNK(ch)对于忽略字符返回true,如果字符为空或tab键值,则字符是可忽略的,否则就是不可忽略的,被ndiff()用来作为linejunk参数的默认值。

difflib综合使用示例

示例演示如何使用difflib创建类似diff的实用程序

""" difflib的命令行接口,提供了以下四种个数的diff:

* ndiff:    列出每一行并突出显示行间更改.
* context:  突出显示更改前后格式中的更改集群.
* unified:  以内联格式简单显示更改集群.
* html:     生成与更改突出显示内容的并排比较.

"""
import sys, os, difflib, argparse
from datetime import datetime, timezone

def file_mtime(path):
    t = datetime.fromtimestamp(os.stat(path).st_mtime, timezone.utc)
    return  t.astimezone().isoformat()

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-c', action='store_true', default=False,
                        help='Produce a context format diff(default)')
    parser.add_argument('-u', action='store_true', default=False,
                        help='Produce a unified format diff')
    parser.add_argument('-m', action='store_true', default=False,
                        help='Produce HTML side by side diff'
                             '(can use -c and -l in conjunction)'
                        )
    parser.add_argument('-n', action='store_true', default=False,
                        help='Produce a ndiff format diff')
    parser.add_argument('-l', '--lines', type=int, default=3,
                        help='Set number of context lines (default 3)')
    parser.add_argument('fromfile')
    parser.add_argument('tofile')
    options = parser.parse_args()
    n = options.lines
    fromfile = options.fromfile
    tofile = options.tofile

    fromdate = file_mtime(fromfile)
    todate = file_mtime(tofile)
    with open(fromfile) as ff:
        fromlines = ff.readlines()
    with open(tofile) as  tf:
        tolines = tf.readlines()

    if options.u:
        diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate,todate, n=n)
    elif options.n:
        diff = difflib.ndiff(fromlines, tolines)
    elif options.m:
        diff = difflib.HtmlDiff().make_file(fromlines, tolines,fromfile, tofile,context=options.c,numlines=n)
    else:
        diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)

    sys.stdout.writelines(diff)

if __name__=='_main_':
    main()    

textwrap模块主要功能是进行文字环绕和填充,模块提供了一些处理函数,以及完成文字环绕填充的TextWrapper类,如果只是包装或填充一个或两个文本字符串,利用模块函数足够,但若处理的文笔长度增加,为了提高处理效率,建议使用TextWrapper类。

textwrap模块函数

textwrap模块提供了以下方法:

wrap()

​ textwrap.wrap(text, width=70, **kwargs)以文本(字符串)形式包装单个段落,使每行的最长宽度为个字符,返回不带最后换行符的输出行列表。可选关键字参数对应于TextWrapper的实例属性,宽度默认为70。

fill()

​ textwrap.fill(text, width=70, **kwargs)以文本形式包装单个段落,并返回包含已包装段落的单个字符串。fill()函数是"\n".join(wrap(text, ...))形式的简略表达方式,实际上,fill()接受与wrap()完全相同的关键字参数。

dedent()

​ textwrap.dedent(text)从文本每一行中删除所有常见的前导空格,可以用来使三个带引号的字符串与显示的左边缘对齐,同时仍然以缩进的形式在源代码中显示。值的注意的是制表符和空白符都被视为空白但并不相等,例如’ hello’、’\thello’都被视为没有常见的前导空白。

>>> import textwrap
>>> def test():
    s = "'\
    hello

SyntaxError: EOL while scanning string literal
>>> def test():
    s = '''\
    hello
      world
    '''
    print(repr(s))
    print(repr(textwrap.dedent(s)))


>>> test()
'    hello\n      world\n    '
'hello\n  world\n'
>>> 

indent()

​ text.wrap.indent(text, prefix, predicate=None)将前缀添加到文本中所选行的开头,行由text.splitlines(True)进行分割,默认情况下,前缀将添加到不单独包含空格(包括任何行尾)的所有行中。

>>> s = 'hello\n\n \nworld'
>>> textwrap.indent(s, '  ')
'  hello\n\n \n  world'
>>> #可选的谓词参数可用于控制缩进的行。例如,很容易将前缀添加到甚至只有空白和空白的行中
>>> print (textwrap.indent(s, '+', lambda line:True))
+hello
+
+ 
+world
>>> 

TextWrapper

TextWrappertextwrap的类,wrap()fill()shorten()通过创建一个TextWrapper实例并对其调用一个方法来工作。该实例不会被重用,因此对于使用wrap()fill()处理许多文本字符串的应用程序,创建自己的TextWrapper对象可能更有效。文本最好用空格包装,并在用连字符连接的单词中的连字符后包装;除非TextWrapper.break_long_words设置为False,这样长单词才不会被破坏。

TextWrapper构建

TextWrapper的构建方式是用模块函数textwrap.TextWrapper(kwargs)来进行构建TextWrapper构造函数接受许多可选关键字参数,每个关键字参数对应一个实例属性。可以多次重复使用同一个TextWrapper对象,并且可以通过在两次使用之间直接分配到实例属性来更改其任何选项。

TextWrapper属性

TextWrapper实例属性(和构造函数的关键字参数)如下:

width

​ width代表换行的最大长度,默认为70,只要输入文本中没有超过宽度的单独单词,TextWrapper就保证输出行不会超过宽度字符。

expand_tabs

​ 如果为true,则使用文本的expandTabs()方法将文本中的所有制表符扩展为空格,默认值为True。

tabsize

​ 如果expand_tabs为True,则根据当前列和给定的选项卡大小,文本中的所有选项卡字符都将扩展为零或更多空格,默认值为8。

replace_whitespace

​ 如果值为True,则在扩展选项卡之后但在包装之前,wrap()方法将用单个空格替换每个空白字符。替换的空白字符如下:制表符、换行符、竖排制表符、换行符和回车符(’\t\n\v\f\r’), 默认值为True。如果expand_tabs为false,replace_whitespace为true,则每个制表符将替换为一个空格,这与制表符扩展不同。如果replace_whitespace为False,换行符可能出现在行的中间并导致奇怪的输出,此时需要将文本拆分为段落(使用str.splitlines()或类似方法),这样这些段落将进行单独包装。

drop_whitespace

​ 如果值为True,则将删除每行开头和结尾的空白(换行后缩进前)。但是,如果后面跟着非空白,则不会删除段落开头的空白;如果删除的空白占用了整行,则会删除整行,默认值为True。

initial_indent

​ 预先准备将包装输出到第一行的字符串。对第一行的长度计数,空字符串不缩进。默认值为’’。

subsequent_indent

​ 准备将包装输出到所有行(第一行除外)前面的字符串。对每行的长度计数,第一行除外,默认值为’’。

fix_sentence_endings

​ 如果为true,则TextWrapper将尝试检测句尾,并确保句子始终由正好两个空格分隔,通常适用于单间距字体的文本。但监测句尾算法并不完善,算法假设句子末尾是由一个小写字母和’.’ 、 ‘!’ 、’?’三种字符中的一种以及后面跟着’’’’或””,再加上一个空格。 这样的情况对于检测像’Dr.’、’Mr.’这样的语句就会出问题。

​ 由于句子检测算法对“小写字母”的定义依赖于string.lowercase,并且在一个句点后使用两个空格分隔同一行上的句子的惯例,因此该属性是特定于英语文本的。

break_long_words

​ 如果值为True,则长度超过宽度的单词将被断开,以确保任何行的长度都不超过宽度。如果值是False的话,长单词就不会被打断,有些行的长度可能会超过宽度。(长单词将自己放在一行上,以最小化超出宽度的量)

break_on_hyphens

​ 如果值为True,包装最好出现在复合词的空白处和连字符后,这在英语中是很常见。如果值为False,则只有空格才可能被视为换行的最佳位置,但是如果需要真正插入的单词,则需要将break_long_words设置为False。在以前的版本中,默认的行为是始终允许断字

max_lines

​ 如果值不是None,则输出将最多包含max_lines行,其中占位符显示在输出的末尾,默认值为None。

placeholder

​ 如果输出文本被截断,将出现在其结尾的字符串,默认值为’ […]’

TextWrapper函数

​ TextWrapper还提供了一些公共方法,类似于模块级的函数功能:

wrap()

wrap(text)以文本(字符串)形式包装单个段落,使每行的宽度为最长宽度个字符。所有包装选项都取自TextWrapper实例的实例属性。返回输出行的列表,不带最后的换行符。如果包装输出没有内容,则返回的列表为空。

fill()

fill(text)以文本形式包装单个段落,并返回包含已包装段落的单个字符串。