将 "hello world" 转换为首字母大写 "Hello World"。
昨晚十一点多,我在公司楼下撸串儿,风有点大,串儿有点糊。我们组那个小李突然问我:哥,“hello world”咋一键变成“Hello World”?我脑子里第一反应是:这不是手到擒来么……但转念一想,坑其实还挺多。算了,我边啃羊肉串边跟你们絮叨两句,顺便把我那段常用的小函数贴一下,回头直接抄走用就完事儿。
就是把一句话里每个英文单词的首字母搞大写,其他字母小写——比如 "hello world" 变 "Hello World"。嗯,就这么简单的需求,落到工程里经常出现在:导入 CSV 抬头清洗、给运营活动标题“洗脸”、把用户随手输的昵称“梳妆打理”。听起来不起眼,但做得糙了就会出笑话,比如 They’re 被弄成 They'Re,iPhone 变成 Iphone,或者一长串空格被你压扁了…哎,都是眼泪。
我当时就跟小李说:先用内置的试试呀,省心。
s = "hello world"
print(s.title()) # Hello World
str.title() 一下就能顶 80% 的场景。可它有两个小毛病,你们注意哈: 一个是带撇号的词,比如
print("they're here".title()) # They'Re Here ← 这个大写有点怪
还有就是“品牌大小写”这种人类审美规则,"iphone x" 你肯定不想得到 "Iphone X"。这就需要你自己兜底。
对了,string.capwords 也能用:
import string
print(string.capwords("hello world")) # Hello World ← 注意多空格会被压成一个
它默认会把一坨空格折叠成一个空格,要保留原样还得自己动手。
我平时用的“顺手刀”:更稳一点的 title_case
我把几件常见小事打包到一个函数里了: 1)首字母大写,其余小写;2)支持英文撇号 ’/';3)支持连字符 -;4)可以定义“介词/连词”之类的小词(a、an、the、of…)中间位置保持小写,但句首句尾还是大写;5)支持例外词典(NASA、API、iPhone 这类固定写法);6)可选是否原样保留多空格与前后空白。
我刚才在串摊纸巾上把核心逻辑掰了掰,你们看代码就懂了:
import re
from typing import Dict, Iterable, Optional
_APOST = "’'"# 常见的两个撇号字符
deftitle_case(
text: str,
*,
minor_words: Optional[Iterable[str]] = None, # 中间小词
exceptions: Optional[Dict[str, str]] = None, # 例外写法映射,key 不区分大小写
preserve_whitespace: bool = True # 是否保留原始空格
) -> str:
"""
把英文片段做“人话”的 Title Case;非英文字母原样返回。
仅对 [A-Za-z] 这类单词做处理,其他文字(比如中文、数字、emoji)不动。
"""
ifnot text:
return text
minor = {w.lower() for w in (minor_words or {
"a","an","the","and","or","for","to","in","on","of","by","at","as","vs","via","from","with","per"
})}
exc_map = {k.lower(): v for k, v in (exceptions or {
"api":"API", "http":"HTTP", "https":"HTTPS", "id":"ID",
"ip":"IP", "ios":"iOS", "iphone":"iPhone", "ipad":"iPad",
"eBay":"eBay", "usa":"USA", "nasa":"NASA"
}).items()}
# 单词的定义:英文字母串,允许内部含撇号或连字符的分段
# 例:rock'n'roll、state-of-the-art 都算一个“复合词”,里头再分别首字母大写
word_pattern = re.compile(r"[A-Za-z]+(?:[-" + _APOST + r"][A-Za-z]+)*")
pieces = []
last_end = 0
defproper_cap(segment: str) -> str:
"""把子段首字母大写,其余小写:e.g. o'reilly -> O'Reilly;state -> State"""
out = []
i = 0
while i < len(segment):
ch = segment[i]
if ch.isalpha():
# 首字母
out.append(ch.upper())
i += 1
# 后续连续的字母全部小写
while i < len(segment) and segment[i].isalpha():
out.append(segment[i].lower())
i += 1
else:
out.append(ch)
i += 1
return"".join(out)
# 找到所有英文“词”,其间的非英文内容(空格、中文、标点)原样拼回
for m in word_pattern.finditer(text):
# 原样补上间隙
if preserve_whitespace:
pieces.append(text[last_end:m.start()])
else:
# 折叠多空格为一个空格(仅当间隙全是空白时)
gap = text[last_end:m.start()]
pieces.append(" "if gap.strip() == ""and gap else gap)
token = m.group(0)
token_key = token.lower()
# 优先例外表(品牌/缩写)
if token_key in exc_map:
pieces.append(exc_map[token_key])
else:
# 连字符内部也逐段首字母大写
parts = re.split(r"(-)", token) # 保留分隔符
cased = "".join(
proper_cap(p) if p != "-"else"-"
for p in parts
)
pieces.append(cased)
last_end = m.end()
# 末尾非英文内容
tail = text[last_end:]
pieces.append(tail if preserve_whitespace else (" "if tail.strip()==""and tail else tail))
out = "".join(pieces)
# 处理“中间小词”——先全体处理,再把中间位置的小词改回小写
# 使用边界匹配,避免把某些缩写(like API)误改
tokens = list(word_pattern.finditer(out))
for i, m in enumerate(tokens):
word = m.group(0)
key = word.lower()
if0 < i < len(tokens)-1: # 不是首尾
if key in exc_map:
continue
if key in minor:
# 保留撇号/连字符形态,只把纯字母段改小写
fixed = re.sub(r"[A-Za-z]+", lambda mm: mm.group(0).lower(), word)
# 替换到原串
out = out[:m.start()] + fixed + out[m.end():]
# 替换后位移会变,为简单起见,重新找一遍(文本通常不长)
tokens = list(word_pattern.finditer(out))
return out
if __name__ == "__main__":
samples = [
"hello world",
" hello world ", # 多空格
"they're not here", # 撇号
"state-of-the-art solution", # 连字符
"from naive bayes to deep learning", # 小词
"iphone x vs. galaxy s24", # 品牌例外
"HELLO WORLD", # 全大写
"你好 hello world 在这儿", # 中英混排
]
for s in samples:
print(repr(s), "=>", repr(title_case(s)))
你们随手跑一下就知道效果咋样了。默认我把 API、NASA、iPhone 这类加进 exceptions 了,你完全可以按你们业务再补充;小词那堆如果团队不同意某几个词保持小写,就把它们从 minor_words 里剔了,别较劲。
刚刚串儿老板把煤加大了,我被呛了一嗓子…咳咳,说人话继续: 1)别拿 capitalize() 干这个活,它只管整句第一个字母大写,剩下全小写,"hello world".capitalize() 只能给你 "Hello world",差了口气。 2)Unicode 其实友好,像西语重音 mañana,上面这版处理会变成 Mañana,没事儿。但希腊文、俄文这种如果要“标题规则”,那是另外一套语言学,别强扭。 3)空格要不要原样保留,看场景。做标题展示,多余空格一般要折叠;做清洗回写数据库,可能就得尊重用户输入。preserve_whitespace 开关就是干这个的。 4)输入全是中文? 别理,原样过。我们只“管”英文字母那部分,省心省力。 5)别迷信“完美规则”,标题大小写本来就没统一国际法——新闻社、APA、芝加哥手册各有各的口味。所以代码给了钩子:小词+例外,交给你们产品/运营拍板就行。
他要的只是最基本那种:
print(title_case("hello world"))
# Hello World
多空格保留:
print(title_case(" hello world ", preserve_whitespace=True))
# " Hello World "
品牌和小词:
print(title_case("from iphone to ipad and back to api docs"))
# From iPhone to iPad and Back to API Docs
撇号和连字符:
print(title_case("rock'n'roll meets state-of-the-art"))
# Rock'n'Roll Meets State-of-the-Art
差不多就这些,能覆盖你们日常 99% 的需求。要是你们那边还有什么奇葩规范——比如 McDonald、O’Neil 那类“家族姓氏规则”——加两条例外就好。我这边串儿快糊透了,先不跟你们叨叨了,等会儿回去我再把这个函数丢到工具库里…
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领