本文讲一讲与python字符串打交道时的一些注意事项,最初是参考python cookbook一书,后来更新了一部分内容。目录如下
- 尽量使用字符串方法而不是re库
- 正则表达式中实现“或”的逻辑(2020年9月7日更新了多字符的或逻辑)
- 字符串拼接
- 文件读写时
- 配合生成器使用
- 字符串反斜杠和正则(2020年9月7日更新新增了这一节)
- python 正则表达式技巧(2020年12月10日更新)
1.尽量使用字符串方法而不是re库
我平常操作字符串时最常用的是join strip split +
这几个方法,如果遇到这几个方法解决不了的事情就去使用re库,如字符串匹配、替换等,甚至都完全忘记字符串本身提供了非常丰富的方法。
所以这里提醒关注find replace startswith endswith
这几个方法,这些基础的方法会比re库快很多,字面匹配用这些比较好,涉及到需要写正则表达式才能匹配下来的情况再找re库
2.正则表达式中实现“或”的逻辑
比如下面拆分出单词,因为分隔符不同,所以要使用“或”的逻辑
import re
line = 'am are; friend, kind,good, foolish'
当你通过“或”的逻辑写下这样的代码时
re.split(';s*|,s*| s*', line)
# ['am', 'are', 'friend', 'kind', 'good', 'foolish']
要想到可以改成这样
re.split('[,; ]s*', line)
# ['am', 'are', 'friend', 'kind', 'good', 'foolish']
另外,如果分隔符想保留下来,可以加一个括号
re.split('([,; ]s*)', line)
# ['am', ' ', 'are', '; ', 'friend', ', ', 'kind', ',', 'good', ', ', 'foolish']
更灵活的方式是用(?:)
,可以实现多字符的“或”逻辑。假设分隔符不是单个字符,则[]
无法使用,如下所示,假如&&
也想作为一个分隔符
import re
line = 'am are; friend, kind,good, foolish&& bc'
re.split('[,; ]s*', line)
# ['am', 'are', 'friend', 'kind', 'good', 'foolish&&', 'bc']
re.split('(?:;|,| |&&)s*', line)
# ['am', 'are', 'friend', 'kind', 'good', 'foolish', 'bc']
这种“或”逻辑在re.findall
等其他功能中也经常使用。
3.字符串拼接
把a b
两个字符串用空格拼接起来一般有三种方法
a = 'abc'
b = 'bcd'
' '.join((a, b))
a + ' ' + b
'{} {}'.format(a, b)
# 'abc bcd'
需要注意的是
(1)不要使用这种写法
s = ''
for p in parts:
s += p
使用加号来连接大量字符是非常低效率的,因为会产生大量中间变量,涉及内存复制和垃圾回收操作
可以将每次新的值append
到列表中,最好是定义在函数里,每次yield
出来,最后得到一个生成器,举例如下
def get_string(a):
yield 'This is a'
if a > 1:
yield 'big'
else:
yield 'small'
yield 'one.'
' '.join(get_string(2))
# 'This is a big one.'
' '.join(get_string(0))
# 'This is a small one.'
(2)避免不必要的拼接
print(a + ':' + b + ':' + c) # Ugly
print(':'.join([a, b, c])) # Still ugly
print(a, b, c, sep=':') # Better
4.文件读写时
我们是应该将字符串拼接得很大一起读进文件里呢,还是一小条一小条读进去呢?
- 如果每一条字符串都很短,那么拼起来读会好一些
- 如果字符串很长,拼起来会占很大内存,则分开读会好一些
5.配合生成器使用
def sample():
yield 'Is'
yield 'Chicago'
yield 'Not'
yield 'Chicago?'
上面的代码是值得推荐的。因为得到的生成器可定制性很高,想对它进行什么处理都可以,如
text = ''.join(sample())
或者
for part in sample():
f.write(part)
上面那么多yield
看起来很别扭,但是想想我们经常把yield
放在循环里,几百几千都用了,这几个不算什么的。
6. 字符串反斜杠和正则
我们来看看下面例子d
和t
的区别
'd' # 'd'
'd' # 'd'
r'd' # 'd'
r'd' #'d'
't' # 't'
r't' # 't'
't' # 't'
解读一下这个结果
t
有特殊的转移含义,在字符串中应看做一个整体- 而
d
应该看做两个字符,因为字符有转义功能,所以用
表示反斜杠这个字符本身
- 如果想表示
t
这两个字符,可以前面加r
或者直接用 'd'
这种写法虽然也不会报错,但它不规范
在正则表达式中d
表示数字,虽然有特殊含义,但它们依然是两个字符。下面例子三种方式都能得到相同的结果,但第一种写法不规范。
import re
line = 'a1b2c'
re.split('d', line) # bad
re.split('d', line) # good
re.split(r'd', line) # good
# ['a', 'b', 'c']
其实就是在写正则时加一个r
的习惯。如果我们想写的pattern中既包含d
又包含t
,直接用r
会将t
理解为t
,但其实这样也可以匹配到t
,看下面这个例子
import re
line = 'a1b2ctd'
re.split(r'd|t', line) # ['a', 'b', 'c', 'd']
re.split('d|t', line) # ['a', 'b', 'c', 'd']
re.split('d|t', line) # ['a', 'b', 'c', 'd']
re.split('d|t', line) # ['a', 'b', 'c', 'd']
re.split('d|t', line) # ['a', 'b', 'c', 'd']
import re
line = r'a1b2ctd'
re.split('d|t', line) # ['a', 'b', 'ctd']
re.split('d|t', line) # ['a', 'b', 'ctd']
re.split('d|t', line) # ['a', 'b', 'c', 'd']
上面结果说明
- 匹配
t
,用t
或t
都可以 - 匹配
t
,必须用t
,因为是在分别匹配三个字符
同理,我们也就很好地理解下面这两个例子
import re
line = 'a1b2cdd'
re.split('d', line) # ['a', 'b', 'cdd']
re.split('d|d', line) # ['a', 'b', 'c', 'd']
line = r'a1b2cdd'
re.split('d', line) # ['a', 'b', 'cdd']
re.split('d|d', line) # ['a', 'b', 'c', 'd']
因为'd'
和r'd'
都相当于'd'
,和上面t
的情形一样。
总结规律:正则表达式匹配的是字符本身,而不是print出来的样子。
- 比如匹配
'd'
或't'
,就看做三个字符,不管它其中一个是用于转义,print出来只有一个反斜杠。 - 比如匹配
't'
,就看做两个字符用t
,不管你print出来是否有特殊含义。而为什么t
也可以呢?因为就像正则中用d
匹配数字一样,正则中也用t
来专门匹配t
。
python正则表达式技巧
. * ?
这三个符号一般都是作用在单个字符上,如果想作用到多个字符上,要这样用(?:abc)*
- “或”用
|
例如匹配的pattern是ab|cd
,则表示ab
或cd
,如果想表示b
或c
,就要这样做a(?:b|c)d
.*?
是匹配除了n
以外的字符,可以用flags=re.S
令它可以匹配所有字符1
可以表示匹配中括号括起来的部分,如果有多个括号,可以用2
或更大的数字。举例:通过替换,进行如下变换
原字符串:[array([1,2,3]), array([[1],[3],[4]])]
新字符串:[[1,2,3], [[1],[3],[4]]]
代码如下
import re
s = '[array([1,2,3]), array([[1],[3],[4]])]'
re.sub(r'array(([.*?]))', r'1', s)
{}()[]
什么时候需要用:在
pattern
中,如果能构成正则的特殊用法,则如果想要表示它本身,就要加;在替换时,
repl
参数不需要加
s = '{} {1}'
re.sub('}', ':', s) # {: {1:'
re.sub('}', ':', s) # {: {1:'
re.sub('{}', ':', s) # ': {1}'
re.sub('{1}', ':', s) # error, pattern 部分不能出现 {1} 形式
re.sub('{1}', ':', s) # '{} :', 要加
re.sub('{1}', '{2}', s) # '{} {2}', repl 部分可以出现 {2} 形式
s = '() (1)'
re.sub(')', ':', s) # error, 对 () 来说,单独出现都要打
re.sub(')', ':', s) # '(: (1:'
re.sub('()', ':', s) # ':(:): :(:1:):', () 被当成空值处理了
re.sub('(1)', ':', s) # '() (:)', 正则表达式中没有 (1) 这种用法,所以没问题
re.sub(')', '(', s) # '(( (1(', repl 中没有问题
s = '[] [1]'
re.sub(']', ':', s) # '[: [1:', 右括号没问题
re.sub('[', ':', s) # error, 左括号不能单独出现
re.sub('[]', ':', s) # error
re.sub('[]', ':', s) # ': [1]', 只需要对左括号打 即可
re.sub('[12]', ':', s) # '[] [:]', 这里表示的是正则的用法而不是它本身
re.sub('[12]', '[]', s) # '[] [[]]', repl 中没有问题
专栏信息
专栏主页:python编程
专栏目录:目录
版本说明:软件及包版本说明