wordpress首页显示文章数量网站排名优化软件有哪些
字符串和文本(三)
- 11.删除字符串中不需要的字符
- 12.审查清理文本字符串
- 13.通过某个关键字排序一个字典列表
- 14.排序不支持原生比较的对象
- 15.通过某个字段将记录分组
11.删除字符串中不需要的字符
你想去掉文本字符串开头,结尾或者中间不想要的字符,比如空白。
strip()
方法能用于删除 开始 或 结尾 的字符。lstrip()
和 rstrip()
分别从左和从右执行删除操作。默认情况下,这些方法会去除空白字符,但是你也可以指定其他字符。比如:
>>> # Whitespace stripping
>>> s = ' hello world \n'
>>> s.strip()
'hello world'
>>> s.lstrip()
'hello world \n'
>>> s.rstrip()
' hello world'
>>>
>>> # Character stripping
>>> t = '-----hello====='
>>> t.lstrip('-')
'hello====='
>>> t.strip('-=')
'hello'
这些 strip()
方法在读取和清理数据以备后续处理的时候是经常会被用到的。比如,你可以用它们来去掉空格,引号和完成其他任务。
但是需要注意的是去除操作不会对字符串的中间的文本产生任何影响。比如:
>>> s = ' hello world \n'
>>> s = s.strip()
>>> s
'hello world'
如果你想处理中间的空格,那么你需要求助其他技术。比如使用 replace()
方法或者是用正则表达式替换。示例如下:
>>> s.replace(' ', '')
'helloworld'
>>> import re
>>> re.sub('\s+', ' ', s)
'hello world'
🚀
re.sub('\s+', ' ', s)
:将所有连续的空白字符替换为单个空格。
\s
:匹配任意空白字符(包括空格、\t
、\n
、\r
等)。+
:表示匹配前面的字符(这里是\s
)一次或多次(即连续多个空白字符)。
通常情况下你想将字符串 strip
操作和其他迭代操作相结合,比如从文件中读取多行数据。如果是这样的话,那么生成器表达式就可以大显身手了。比如:
with open(filename) as f:lines = (line.strip() for line in f)for line in lines:print(line)
在这里,表达式 lines = (line.strip() for line in f)
执行数据转换操作。 这种方式非常高效,因为它不需要预先读取所有数据放到一个临时的列表中去。它仅仅只是创建一个生成器,并且每次返回行之前会先执行 strip
操作。
对于更高阶的 strip
,你可能需要使用 translate()
方法。
12.审查清理文本字符串
一些无聊的幼稚黑客在你的网站页面表单中输入文本 pýtĥöñ
,然后你想将这些字符清理掉。
文本清理问题会涉及到包括文本解析与数据处理等一系列问题。在非常简单的情形下,你可能会选择使用字符串函数(比如 str.upper()
和 str.lower()
)将文本转为标准格式。使用 str.replace()
或者 re.sub()
的简单替换操作能删除或者改变指定的字符序列。你同样还可以使用 unicodedata.normalize()
函数将 Unicode 文本标准化。
然后,有时候你可能还想在清理操作上更进一步。比如,你可能想消除整个区间上的字符或者去除变音符。为了这样做,你可以使用经常会被忽视的 str.translate()
方法。为了演示,假设你现在有下面这个凌乱的字符串:
>>> s = 'pýtĥöñ\fis\tawesome\r\n'
>>> s
'pýtĥöñ\x0cis\tawesome\r\n'
第一步是清理空白字符。为了这样做,先创建一个小的转换表格然后使用 translate()
方法:
>>> remap = {
... ord('\t') : ' ',
... ord('\f') : ' ',
... ord('\r') : None # Deleted
... }
>>> a = s.translate(remap)
>>> a
'pýtĥöñ is awesome\n'
正如你看的那样,空白字符 \t
和 \f
已经被重新映射到一个空格。回车字符 r
直接被删除。
你可以以这个表格为基础进一步构建更大的表格。比如,让我们删除所有的和音符:
>>> import unicodedata
>>> import sys
>>> cmb_chrs = dict.fromkeys(c for c in range(sys.maxunicode)
... if unicodedata.combining(chr(c)))
...
>>> b = unicodedata.normalize('NFD', a)
>>> b
'pýtĥöñ is awesome\n'
>>> b.translate(cmb_chrs)
'python is awesome\n'
unicodedata.combining(chr(c))
:检查 Unicode 码点c
对应的字符是否为 组合字符(如音调符号´
、分音符¨
等)。如果是,返回True
。dict.fromkeys(...)
:将所有组合字符的 Unicode 码点作为键,值设为None
,生成一个字典。为后续translate()
方法提供需删除的字符映射表。unicodedata.normalize('NFD', a)
:将字符串a
转换为 Unicode 分解形式(NFD),即把带重音的字符(如ñ
)拆解为 基础字符 + 组合符号(n
+~
)。translate(cmb_chrs)
:根据字典cmb_chrs
的键(组合字符的码点),将字符串b
中所有匹配的字符替换为None
(即删除)。
上面例子中,通过使用 dict.fromkeys()
方法构造一个字典,每个 Unicode 和音符作为键,对应的值全部为 None 。
然后使用 unicodedata.normalize()
将原始输入标准化为分解形式字符。然后再调用 translate
函数删除所有重音符。同样的技术也可以被用来删除其他类型的字符(比如控制字符等)。
作为另一个例子,这里构造一个将所有 Unicode 数字字符映射到对应的 ASCII 字符上的表格:
>>> digitmap = { c: ord('0') + unicodedata.digit(chr(c))
... for c in range(sys.maxunicode)
... if unicodedata.category(chr(c)) == 'Nd' }
...
>>> len(digitmap)
460
>>> # Arabic digits
>>> x = '\u0661\u0662\u0663'
>>> x.translate(digitmap)
'123'
unicodedata.category(chr(c)) == 'Nd'
:检查 Unicode 码点c
对应的字符是否属于 十进制数字(Nd
类别),例如:- 阿拉伯数字
١
(U+0661) - 印度数字
१
(U+0661) - 中文数字
1
(U+FF11) - 这些字符虽然形状不同,但都表示 1。
- 阿拉伯数字
unicodedata.digit(chr(c))
:返回该数字字符的 数值(如١
返回 1,२
返回 2)。ord('0') + unicodedata.digit(chr(c))
:计算该数字对应的 ASCII 码点:ord('0')
是 48(ASCII 的0
)١
的数值是 1 → 48 + 1 = 49(即1
的 ASCII 码点)
x
是字符串١٢٣
(Unicode 码点U+0661
,U+0662
,U+0663
)。translate(digitmap)
根据字典将每个字符替换为对应的 ASCII 数字:١
(U+0661
)→1
٢
(U+0662
)→2
٣
(U+0663
)→3
另一种清理文本的技术涉及到 I/O 解码与编码函数。这里的思路是先对文本做一些初步的清理,然后再结合 encode()
或者 decode()
操作来清除或修改它。比如:
>>> a
'pýtĥöñ is awesome\n'
>>> b = unicodedata.normalize('NFD', a)
>>> b.encode('ascii', 'ignore').decode('ascii')
'python is awesome\n'
这里的标准化操作将原来的文本分解为单独的和音符。接下来的 ASCII 编码/解码只是简单的一下子丢弃掉那些字符。当然,这种方法仅仅只在最后的目标就是获取到文本对应 ACSII 表示的时候生效。
文本字符清理一个最主要的问题应该是运行的性能。一般来讲,代码越简单运行越快。对于简单的替换操作,str.replace()
方法通常是最快的,甚至在你需要多次调用的时候。比如,为了清理空白字符,你可以这样做:
def clean_spaces(s):s = s.replace('\r', '')s = s.replace('\t', ' ')s = s.replace('\f', ' ')return s
如果你去测试的话,你就会发现这种方式会比使用 translate()
或者正则表达式要快很多。
另一方面,如果你需要执行任何复杂字符对字符的重新映射或者删除操作的话,translate()
方法会非常的快。
从大的方面来讲,对于你的应用程序来说性能是你不得不去自己研究的东西。不幸的是,我们不可能给你建议一个特定的技术,使它能够适应所有的情况。因此实际情况中需要你自己去尝试不同的方法并评估它。
尽管这一节集中讨论的是文本,但是类似的技术也可以适用于字节,包括简单的替换,转换和正则表达式。
🚀 应用场景
- 国际化文本处理:将各种语言的数字统一为 ASCII 格式。
- 数据清洗:确保数字符号的一致性(如爬取多语言网页时)。
- 安全校验:防止混淆字符攻击(如用
1
(全角)冒充1
)。
13.通过某个关键字排序一个字典列表
你有一个字典列表,你想根据某个或某几个字典字段来排序这个列表。
通过使用 operator
模块的 itemgetter
函数,可以非常容易的排序这样的数据结构。假设你从数据库中检索出来网站会员信息列表,并且以下列的数据结构返回:
rows = [{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]
根据任意的字典字段来排序输入结果行是很容易实现的,代码示例:
from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))
print(rows_by_fname)
print(rows_by_uid)
代码的输出如下:
[{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'}]
[{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'}]
itemgetter()
函数也支持多个 keys
,比如下面的代码:
rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
print(rows_by_lfname)
会产生如下的输出:
[{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'}]
在上面例子中,rows
被传递给接受一个关键字参数的 sorted()
内置函数。这个参数是 callable
类型,并且从 rows
中接受一个单一元素,然后返回被用来排序的值。itemgetter()
函数就是负责创建这个 callable
对象的。
operator.itemgetter()
函数有一个被 rows
中的记录用来查找值的索引参数。可以是一个字典键名称,一个整形值或者任何能够传入一个对象的 __getitem__()
方法的值。如果你传入多个索引参数给 itemgetter()
,它生成的 callable
对象会返回一个包含所有元素值的元组,并且 sorted()
函数会根据这个元组中元素顺序去排序。但你想要同时在几个字段上面进行排序(比如通过姓和名来排序,也就是例子中的那样)的时候这种方法是很有用的。
itemgetter()
有时候也可以用 lambda
表达式代替,比如:
rows_by_fname = sorted(rows, key=lambda r: r['fname'])
rows_by_lfname = sorted(rows, key=lambda r: (r['lname'],r['fname']))
这种方案也不错。但是,使用 itemgetter()
方式会运行的稍微快点。因此,如果你对性能要求比较高的话就使用 itemgetter()
方式。
最后,不要忘了这节中展示的技术也同样适用于 min()
和 max()
等函数。比如:
>>> min(rows, key=itemgetter('uid'))
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
>>> max(rows, key=itemgetter('uid'))
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
14.排序不支持原生比较的对象
你想排序类型相同的对象,但是他们不支持原生的比较操作。
内置的 sorted()
函数有一个关键字参数 key
,可以传入一个 callable
对象给它,这个 callable
对象对每个传入的对象返回一个值,这个值会被 sorted
用来排序这些对象。比如,如果你在应用程序里面有一个 User
实例序列,并且你希望通过他们的 user_id
属性进行排序,你可以提供一个以 User
实例作为输入并输出对应 user_id
值的 callable
对象。比如:
class User:def __init__(self, user_id):self.user_id = user_iddef __repr__(self):return 'User({})'.format(self.user_id)def sort_notcompare():users = [User(23), User(3), User(99)]print(users)print(sorted(users, key=lambda u: u.user_id))
另外一种方式是使用 operator.attrgetter()
来代替 lambda
函数:
>>> from operator import attrgetter
>>> sorted(users, key=attrgetter('user_id'))
[User(3), User(23), User(99)]
选择使用 lambda
函数或者是 attrgetter()
可能取决于个人喜好。但是,attrgetter()
函数通常会运行的快点,并且还能同时允许多个字段进行比较。这个跟 operator.itemgetter()
函数作用于字典类型很类似。例如,如果 User
实例还有一个 first_name
和 last_name
属性,那么可以向下面这样排序:
by_name = sorted(users, key=attrgetter('last_name', 'first_name'))
同样需要注意的是,这一小节用到的技术同样适用于像 min()
和 max()
之类的函数。比如:
>>> min(users, key=attrgetter('user_id'))
User(3)
>>> max(users, key=attrgetter('user_id'))
User(99)
15.通过某个字段将记录分组
你有一个字典或者实例的序列,然后你想根据某个特定的字段比如 date
来分组迭代访问。
itertools.groupby()
函数对于这样的数据分组操作非常实用。为了演示,假设你已经有了下列的字典列表:
rows = [{'address': '5412 N CLARK', 'date': '07/01/2012'},{'address': '5148 N CLARK', 'date': '07/04/2012'},{'address': '5800 E 58TH', 'date': '07/02/2012'},{'address': '2122 N CLARK', 'date': '07/03/2012'},{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},{'address': '1060 W ADDISON', 'date': '07/02/2012'},{'address': '4801 N BROADWAY', 'date': '07/01/2012'},{'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]
现在假设你想在按 date
分组后的数据块上进行迭代。为了这样做,你首先需要按照指定的字段(这里就是 date
)排序, 然后调用 itertools.groupby()
函数:
from operator import itemgetter
from itertools import groupby# Sort by the desired field first
rows.sort(key=itemgetter('date'))
# Iterate in groups
for date, items in groupby(rows, key=itemgetter('date')):print(date)for i in items:print(' ', i)
运行结果:
07/01/2012{'date': '07/01/2012', 'address': '5412 N CLARK'}{'date': '07/01/2012', 'address': '4801 N BROADWAY'}
07/02/2012{'date': '07/02/2012', 'address': '5800 E 58TH'}{'date': '07/02/2012', 'address': '5645 N RAVENSWOOD'}{'date': '07/02/2012', 'address': '1060 W ADDISON'}
07/03/2012{'date': '07/03/2012', 'address': '2122 N CLARK'}
07/04/2012{'date': '07/04/2012', 'address': '5148 N CLARK'}{'date': '07/04/2012', 'address': '1039 W GRANVILLE'}
groupby()
函数扫描整个序列并且查找连续相同值(或者根据指定 key
函数返回值相同)的元素序列。在每次迭代的时候,它会返回一个值和一个迭代器对象,这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。
一个非常重要的准备步骤是要根据指定的字段将数据排序。因为 groupby()
仅仅检查连续的元素,如果事先并没有排序完成的话,分组函数将得不到想要的结果。
如果你仅仅只是想根据 date
字段将数据分组到一个大的数据结构中去,并且允许随机访问,那么你最好使用 defaultdict()
来构建一个多值字典。比如:
from collections import defaultdict
rows_by_date = defaultdict(list)
for row in rows:rows_by_date[row['date']].append(row)
这样的话你可以很轻松的就能对每个指定日期访问对应的记录:
>>> for r in rows_by_date['07/01/2012']:
... print(r)
...
{'date': '07/01/2012', 'address': '5412 N CLARK'}
{'date': '07/01/2012', 'address': '4801 N BROADWAY'}
在上面这个例子中,我们没有必要先将记录排序。因此,如果对内存占用不是很关心,这种方式会比先排序然后再通过 groupby()
函数迭代的方式运行得快一些。