1. 写在前面

今天这篇文章整理python有关字符串的一些知识, 主要包括字符串的一些基本操作, 像字符串的翻转, 切片, 串联, 分割, 替换等。 然后再整理正则的一些基本知识。依然是从使用的角度, 并后期不断补充。
Ok, let’s go!

2. 字符串的基本操作

python字符串的基本操作还是有必要掌握的, 并且非常有用, 常见的像字符串的反转, 切片, 串联, 分割替换等。

s = 'python'

##反转操作有两种方式
re = ''.join(reversed(s))
re1 = s[::-1]    # 'nohtyp'

# 字符串的切片操作   
# 在指定的索引处生成字符串
# FizzBuzz是一个简单的小游戏。游戏规则如下:从1开始往上数数,
# 当遇到3的倍数的时候,说fizz,当遇到5的倍数,说buzz,当遇到15的倍数,就说fizzbuzz,其他情况下则正常数数。

java, python = 'fizz', 'buzz'

jl, pl = len(java), len(python)

[str(java[i%3*jl:]+python[i%5*pl:] or i) for i in range(1, 20)]    # 这个写法牛逼

字符串的串联与分割, joinsplit可以看做一对互逆操作。

# join串联字符串  
mystr = ['I', 'love', 'python']
res = '_'.join(mystr)   # 下划线_连接mystr     'I_love_python'

# 字符串分割split   根据指定的字符或者字符串   join和split可以看做一对互逆操作
res.split('_')    # ['I', 'love', 'python']

字符串的替换操作replace

# 替换replace
# 把下面的小写o换成大写的O
s = 'i love python'.replace('o', 'O')
s # 'i lOve pythOn'

判断串a是否是串b的子串

# 法一:  使用in
a = 'our'
b = 'flour'
a in b

# 法二: 使用find直接返回a的最小索引
c = 'mo'
b.find(a)      # 2
b.find(c)   # -1  表示没找到, 就不是子串

strip()函数去掉字符串两段的空格

# 去空格  清洗字符串时,位于字符串开始和结尾的空格,有时需要去掉,strip 方法能实现。
a = '    \tI love python \b\n'
a.strip()  # 'I love python \x08'

# 字符串的字节长度
len('I love python')    # 13
len('我爱中国 ')  # 5

3. 正则初探

字符串封装的方法,处理一般的字符串操作,还能应付。但是,稍微复杂点的字符串处理任务,需要靠正则表达式,简洁且强大。

首先, 要认识常用的元字符:

  • .: 匹配除"\n"和"\r"之外的任何单个字符。
  • ^: 匹配字符串开始位置
  • $: 匹配字符串中结束的位置
  • *: 前面的原子重复0次、1次、多次
  • ?: 前面的原子重复0次或者1次
  • +: 前面的原子重复1次或者多次
  • {n}: 前面的原子出现了n次
  • {n,}: 前面的原子至少出现n次
  • {n, m}: 前面原子出现次数介于n-m之间
  • ()分组, 输出需要的部分

再认识常用的通用字符:

  • \s: 匹配空白字符
  • \w: 匹配任意字母/数字/下划线
  • \W: 和小写w相反, 匹配任意字母/数字/下划线以外的字符
  • \d: 匹配十进制数字
  • \D: 匹配除了十进制以外的值
  • [0-9]: 匹配一个0-9之间的数字
  • [a-z]: 匹配小写英文字母
  • [A-Z]: 匹配大写英文字母

正则表达式,常会涉及到以上这些元字符或通用字符,下面通过 14 个细分的与正则相关的小功能,讨论正则表达式。

首先先导入包:import re

3.1 search和match

search方法, 找到子串中第一个匹配的位置, 在字符串的任意位置进行匹配, 而match方法啊, 只在原字符串的开始位置匹配。

s = 'flourish'

# 寻找模式串our, 使用match方法
recom = re.compile('our')
m = recom.match(s)   # # 返回 None,找不到匹配, 这是因为match只能匹配以our开头的字符串, 比如ourself
type(m)

# search方法
res = recom.search(s)
res.span()  # OK, 匹配成功,our 在原字符串的起始索引为 2  (2, 5)

s1 = 'ourselfves'

recom = re.compile('our')  # 编译一个正则表达式模式,返回一个模式对象
m = recom.match(s1)
m.span()

3.2 finditer匹配迭代器

使用正则模块,finditer 方法,返回所有子串匹配位置的迭代器。 通过返回的对象 re.Match,使用它的方法 span() 找出匹配位置。

s = 'hello world hello how are you helloool'
pat = 'hello'

r = re.finditer(pat, s)
for i in r:
    print(i.span())

## 结果   三个hello的起始和终止字符位置
(0, 5)    
(12, 17)
(30, 35)

3.3 findall所有匹配

正则模块,findall 方法能查找出子串的所有匹配。

# 我这里有个字符串, 目标时找出所有数字
s = '一共20行代码运行时间13.59s'

pat = r'\d+'     # 这个表示找里面的数字
r = re.findall(pat, s)
print(r)     # 找到了['20', '13', '59'],  但是我们期望找到[20, 13.59]

所以上面我们的正则表达式写法有点问题, 可以简单分析一下, 如果找出上面的20和13.50呢?

案例1: 匹配浮点数和整数

  • ?: 表示前一个字符匹配0次或者1次
  • .?: 表示匹配小数点0次或者1次

匹配浮点数和整数的第一版正则表达式: r'\d+\.?\d+'

python cx_Freeze 教程 pythonzero_正则表达式

# 所以需要修改正则表达式
s = '一共20行代码运行时间13.59s'

pat = r'\d+\.?\d+'
r = re.findall(pat, s)
r # ['20', '13.59']

但是上面这种写法有个问题, 由于出现了两个\d+, 这个表示至少有一位数字, 因此上面的表达式至少匹配两位数。 所以需要把后面的+改成*。

python cx_Freeze 教程 pythonzero_贪心捕获与非贪心_02

s = '一共2行代码运行时间13.59s'

pat = r'\d+\.?\d+'
r = re.findall(pat, s)
r      # 13.59

pat1 = r'\d+\.?\d*'
r1 = re.findall(pat1, s)
r1    # ['2', '13.59']

案例二: 匹配所有正整数
下面三种方式, 先分析一下哪个正确:

  • ^\d*$: 这个会匹配到0
  • ^[1-9]*: 会匹配到1.的1, 不是完全匹配
  • ^[1-9]\d*$: 这个是正确的

    代码如下:
s = [-16, 1.5, 11.43, 10, 5]
pat = r'^[1-9]\d*$'
[i for i in s if re.match(pat, str(i))]   # [10, 5]

3.4 re.I 忽略大小写

查找字符串中字符位置的时候, re.I是方法的可选参数, 表示忽略大小写

s = 'That'
pat = r't'

r = re.finditer(pat, s, re.I)
for i in r:
	print(i.span())       # (0,1) (3,4)

3.5 split分割单词

正则模块中 split 函数强大,能够处理复杂的字符串分割任务。如果一个规则简单的字符串, 直接使用字符串的split函数

s = 'id\tname\taddress'

# 根据字符串\t分割
s.split('\t')      # ['id', 'name', 'address']

字符串中的split函数只能处理一些简单的分割操作, 对于复杂的字符串, 就需要用到正则里面的split。

# 但是对于复杂的字符串, split函数就不行了
s = 'This,,,   module ; \t   provides|| regular;'

# 这个如果要出单词来的话, 就得研究研究字符串的规则了。 每个单词用了不同的符号进行分割, 并且有的出现还不止一次
# 所以得用个正则表达式匹配这些符号  看一下有[,\s;|]这是那些字符,\s表示空字符。 有的还不止出现一次, 那么就是[,\s;|] +
words = re.split('[,\s;|]+', s)
words   # ['This', 'module', 'provides', 'regular', '']

3.6 sub替换字符串

正则模块,sub 方法,替换匹配到的子串。

content = 'hello 1234, hello 1234'
pat = re.compile(r'\d+')     # 表示一位或者多位的十进制数
m = pat.sub('555', content)
m        # 'hello 555, hello 555'

3.7 compile预编译

如果要用同一匹配模式, 做很多次匹配, 可以使用compile预先编译串。

案例:从一系列字符串中,挑选出所有正浮点数

正则表达式为:^[1-9]\d.\d|0.\d*[1-9]\d*$,字符 a|b 表示 a 串匹配失败后,才执行 b 串,正则分解图见下:

python cx_Freeze 教程 pythonzero_正则表达式_03


代码如下:

s = [-16, 'good', 1.5, 0.2, -0.1, '11.43', 10, '5e10', .8, 8., '0.0']

rec = re.compile(r'^[1-9]*\.\d*|0\.\d*[1-9]\d*$')

[i for i in s if rec.match(str(i))]    # [1.5, 0.2, '11.43', 0.8, 8.0]

3.8 贪心捕获

正则模块中,根据某个模式串,匹配到结果。

content = """<h>ddedadsad</h><div>graph</div><div>dafaf</div><div>math</div>"""

# 下面匹配<div>标签里的内容
result = re.findall(r'<div>.*</div>', content)
result   # ['<div>graph</div><div>dafaf</div><div>math</div>']

# 如果不想保留开始和结尾的<div></div>, 需要用一对()去捕获
result = re.findall(r'<div>(.*)</div>', content)
result        # 少了两头的<div></div>

(.*) 表示捕获任意多个字符, 尽可能多的匹配字符, 也被称为贪心捕获。 .表示匹配除换行外的任意字符。 所以上面的结果中,会把div、/div也看成字符

python cx_Freeze 教程 pythonzero_python字符串_04

3.9 非贪心捕获

上面如果只想得到<div></div>中间的内容, 可以使用非贪心捕获。

result = re.findall(r'<div>(.*?)</div', content)
result   # ['graph', 'dafaf', 'math']

(.*?): 被称为非贪心捕获, 加了个问号, 就表示着要捕捉前面的这种模式0次或者多次, 这时候()分组的时候,就把div, /div分成了一组, 里面的内容分成了一组。 可以和上面的贪心对比一下, 上面的贪心在分组的时候, 只把最外面的一组div, /div单独分成了一组。

下面对比一下贪心捕获和非贪心捕获:

s = 'abcabdecd'
res = re.findall(r'ab.*?c', s) # 非贪婪
res1 = re.findall(r'ab.*c', s)  # 贪婪
res  # ['abc', 'abdec']
#res1  # ['abcabdc']

感觉贪婪搜索想一遍找完尽可能多的, 而非贪婪搜索多次找尽可能少的。

比如上面这个例子, 贪婪搜索的匹配过程是看到了前两个字符ab, 然后看到了第一个c, 做个标记,这时候其实匹配成功, 但是它不死心, 依然往后找, 想看看还有没有c, 这样能匹配更多的字符, 结果看到了后面的c, 挺高兴,做个标记,但依然不死心,还往后找, 发现后面没有能匹配的了,所以就返回来了上次做标记的那一串。但这样也比单纯的abc长。

非贪婪搜索是这样的: 从头开始看到了ab, 然后又看到了c很知足了,先返回来一个结果abc, 然后再从返回的这个地方再往后找, 又看到了ab,再往后看到了c, 又符合规则, 再返回一个结果来abdc, 再从返回的地方往后找发现没了,结束。

https://regex101.com/ 这个网站上输入字符串, 输入写的正则表达式就可以匹配出字符,这样就能看到自己写的正则表达式是不是正确

3. python的string模块

这里面有一些非常常用的字符长处理函数和方法, 如果知道的话,对于日常的应用还是非常方便的。这里记录一些常用方法和常用的字符串常量。

3.1 常用方法

查找:

  • str.count(s) 返回字符串s在str中出现的次数
  • str.endswith(s) 判断字符串str是否以字符串s结尾
  • str.find(s) 返回字符串s在字符串str中的位置索引,没有则返回-1
  • str.index(s) 和find()方法一样,但是如果s不存在于str中则会抛出异常
  • str.rfind(s) 类似于 find()函数,不过是从右边开始查找
  • str.rindex(s) 类似于 index(),不过是从右边开始

判断:

  • str.isalnum() 如果str至少有一个字符并且都是字母或数字则返回True,否则返回False
  • str.isalpha() 如果str至少有一个字符并且都是字母则返回True,否则返回False
  • str.isdigit() 如果str只包含数字则返回 True 否则返回 False
  • str.islower() 如果str存在区分大小写的字符,并且都是小写则返回True 否则返回False
  • str.isupper() 如果str存在区分大小写的字符,并且都是大写则返回True 否则返回False

转换:

  • str.capitalize() 把字符串的首字母大写
  • str.lower() 转换str中所有大写字符为小写
  • str.upper() 返回str所有字符为大写的字符串
  • str.lstrip() 去掉str左边的不可见字符
  • str.strip() 等于同时执行rstrip()和lstrip()
  • str.replace(a, b) 将字符串str中的a替换成b

切分:

  • str.split(s) 以s为分隔符切片str
  • str.partition(s) 用s将str切分成三个值

3.2 字符串常量

  • string.ascii_lowercase 小写字母’abcdefghijklmnopqrstuvwxyz’
  • string.ascii_uppercase 大写的字母’ABCDEFGHIJKLMNOPQRSTUVWXYZ’
  • string.digits 数字0到9的字符串:’0123456789’