Python删除字符串后缀问题
文章目录
问题发现
我的 GitHub Pages 博客 clownote 是通过 Hexo + 一些自己写的本地脚本 + Github Action 来自动发布的(参考鄙人拙作《还在手动发博客?GitHub Actions自动化真香》)。
文章修改的 GitHub 提交信息是通过一个脚本自动生成的,它会列出修改的文章名对于具体的文章(都是 markdown),会忽略后缀 .md
,这样好看一些,而其他文件(工具脚本)则保留完整文件名:
前几天写了篇名叫 Intro_sham.md
的文章,即《我这人不懂什么操作系统,于是用Go语言模拟出了一个》,它的提交信息是:
changed files: Intro_sha
莫名缺了一个 m
。。。
问题定位
打开工具脚本,找到写 Git 提交 message 的代码在这里:
def push(**kwargs):
...
names = map(lambda f: os.path.basename(f).rstrip('.md'), changed)
commit_msg = 'changed files: ' + ', '.join(names)
...
changed files
的名字是通过一个作用于所有改变了的文件的 map 方法得到的。这个 map 做的工作是把文件的路径和 .md
后缀去掉(只是为了好看)。
问题呼之欲出 —— rstrip
的错误使用。
问题复现
>>> s = "emmm.md"
>>> s.rstrip('.md')
'e'
RTFM
看一看 rstrip 的文档:https://docs.python.org/3.7/library/stdtypes.html#str.rstrip
str.rstrip([chars])
里面写了:
Return a copy of the string with trailing characters removed.
The chars argument is a string specifying the set of charactersto be removed.
The chars argument is not a suffix; rather, all combinations of its values are stripped.
是结尾处有你给的 chars 参数里字符的任意组合都会被删。
解决方案
这个问题其实是个长期以来的痛点,* 相关问题阅读量破万,问题活跃时长超过 7 年。以前我就碰到过,当时写这里的时候就有点感觉,似乎用 rstrip 好像不太妥当,但随便测试了几个例子没问题也就这么用了。
正则
解决这种问题的一个方法是用正则:
import re
re.sub('^' + re.escape(prefix), '', s) # 删前缀
re.sub(re.escape(suffix) + '$', '', s) # 删后缀
要导个包,麻烦。代码可读性也低,效率…估计也不怎么样。
切片
另一种方法是用切片解决,代码可以封装地好看一点:
# Reference https://www.python.org/dev/peps/pep-0616/
def removeprefix(self: str, prefix: str, /) -> str:
if self.startswith(prefix):
return self[len(prefix):]
else:
return self[:]
def removesuffix(self: str, suffix: str, /) -> str:
# suffix='' should not call self[:-0].
if suffix and self.endswith(suffix):
return self[:-len(suffix)]
else:
return self[:]
官方
实际上,为了解决这种问题,PEP 616 – String methods to remove prefixes and suffixes 提出了专门用来删前缀/后缀的方法,这是 Python 3.9 的新特性:
s.removeprefix('prefix')
s.removesuffix('suffix')
P.S. 官方的 C 实现还是很有意思的,可以去看看:cpython/pull/18939 。
但这里我的环境是 Python 3.7,所以只能用前两种方法手撸一个啦。
TL;DR 太长不看
用 str.rstrip
来删除字符串后缀是错误的,这个方法会删除参数中各字符的任意组合。
>>> s = "emmm.md"
>>> s.rstrip('.md')
'e'
解决方法:
- 用 Python 3.9:
s.removesuffix('suffix')
- 正则表达式:
re.sub(re.escape(suffix) + '$', '', s)
- endswith + 切片:
def removesuffix(s: str, suffix: str) -> str:
if suffix and self.endswith(suffix):
return self[:-len(suffix)]
else:
return self[:]
好了,这节课我们不往下讲了,剩下的时间来做个小练习,看看今天的知识大家学废了吗。。。(‘蠢’.capitalize())
# See you!
'CDFMLR 2020-11-24 11:22'.rstrip(':2333')