做文本清洗的时候我经常被 Python 字符串处理整得又好气又好笑。明明是个很基础的功能比如把一段日志里的数字提出来、把倒序的订单号翻回来、或者从一长串字符里挑出真正有用的字母数字写起来总有那么一两个“意外”。这篇是 Python 字符串系列的第二个部分我会把那些真正在日常开发、脚本处理、小工具里高频出现的字符串操作从逆序、排序、分割、判断字符类型到格式化输出全部串一遍。适合已经会写s[0]、len(s)这种基础语法但还想把字符串处理用得顺手、写出优雅代码的朋友。先说好这篇文章不追求把所有 API 都列一遍重点是三个东西底层机制怎么理解、常用操作怎么写才不踩坑、哪些写法在真实场景里更抗造。我会用手里的实际代码片段来说明而不是教科书式的例子。1. 先搞懂Python字符串的“铁律”不可变与直接赋值1.1 为什么字符串不能原地修改一次 id() 实验看清楚很多人学 Python 字符串时被“不可变”三个字整懵了。明明写s hello之后再写s world就成功了凭什么说不能改这里要先分清楚两个概念修改对象和重新绑定变量名。字符串对象本身是一个不可变的数据块一旦创建里面的字符序列就不会变了。你可以把变量名想象成贴在对象上的便利贴s world不是把原来那个hello对象改成world而是撕下便利贴重新贴到一个新的字符串对象上。原来的hello还在内存里只是没了引用会被垃圾回收。用id()函数可以看得明明白白s hello print(id(s)) # 某个内存地址比如 140372345402736 s s.replace(h, H) print(id(s)) # 地址变了说明是新的对象如果你非要去改字符串里的某个字符比如s[0] HPython 会直接抛TypeError: str object does not support item assignment。这一点和列表完全不同列表是可变对象支持lst[0] 100这种原地修改。为什么字符串要设计成不可变核心原因是安全性和性能优化。字符串通常会被用作字典的键如果键能被原地改掉整个哈希表就乱了。另外不可变对象在并发场景下天然线程安全Python 内部也可以放心地把同一个字符串对象共享给多个变量比如字符串驻留机制就是靠不可变才成立的。1.2 “直接赋值更改”到底改了什么重新绑定与内存真相很多搜索“python字符串直接赋值更改”的人其实是遇到了这种困惑明明我写了s abc然后s s d感觉就像是“更改”成功了为什么文档又说字符串不可变答案还是一样s s d做了两件事。先计算s d这会生成一个新的字符串对象abcd然后把变量名s重新指向这个新对象。原来的abc没有被改动也没有消失只是不再被s引用。如果你只是给字符串做一次拼接这种写法没有任何问题。但在循环里反复执行s item就得小心了每次拼接都会创建一个新对象循环 N 次就创建 N 个中间字符串对象内存和时间都会白费。后面讲到join()的时候我会给一套更好的方案。还有一个相关概念叫“原地修改方法”的错觉。字符串方法里strip()、replace()、upper()、lower()全部是返回新字符串不会改动原字符串。所以你经常要写s s.strip()才能“生效”这个等号不能省。很多新手在这个地方漏掉返回值导致怎么处理都是原样输出。1.3 常用“返回新字符串”的方法清单既然字符串不可变所有“处理”字符串的操作都应该理解成“返回一个处理后的新字符串”。我把最高频的几个方法列成一张表方便你查操作方法示例返回值去除两端空白strip() hi .strip()hi替换子串replace(old, new)a-b.replace(-, /)a/b全部转大写upper()hi.upper()HI全部转小写lower()HI.lower()hi首字母大写title()hello world.title()Hello World按分隔符拆分split(,)a,b.split(,)[a, b]拼接序列..join([a,b])a.b字符串反转字符串切片[::-1]abc[::-1]cba判断前缀后缀startswith()/endswith()abc.startswith(ab)True这些小操作单独看都很简单但组合起来就是文本处理的基本功。下面几节就逐个展开先讲最经典的字符串逆序。2. 字符串逆序一行切片 vs 常用循环写法2.1 s[::-1] 切片逆序的原理与边界字符串逆序在 Python 里几乎是一行代码的事s python print(s[::-1]) # nohtyp这个写法的完整说法是“切片逆序”。切片s[start:stop:step]里step为 -1 表示从右往左取字符start和stop默认是两端所以直接得到了完整的逆序结果。很多人会问为什么不是s[-1::-1]或者s[len(s)-1::-1]其实这两种也能跑但都不如s[::-1]简洁。切片逆序对任意字符都有效中文、英文、标点符号混在一起也一样因为 Python 字符串按字符索引不会按照字节切这点比 C 语言那套基于char[]的思路省心太多。需要注意一个边界s[::-1]不会改变字符本身的字节编码只是把顺序倒过来。如果你拿到的文本里有组合字符比如带声调符号的欧洲字母直接逆序可能会把组合记号的位置弄乱但 99% 的中英文场景不用纠结这个问题。2.2 reversed() join 的另一种写法有时候不一定要真的生成一个逆序字符串只是想“从后往前遍历”这时用reversed()更合适s python for ch in reversed(s): print(ch) # n o h t y p如果确实需要逆序后的字符串再用join拼回来s python rev .join(reversed(s)) print(rev) # nohtyp从可读性上来说s[::-1]是最直接的几乎所有 Python 程序员一眼就懂。reversed()join的好处是可以配合一些条件做过滤比如“逆序的同时把空格去掉”s a b c rev_no_space .join(ch for ch in reversed(s) if not ch.isspace()) print(rev_no_space) # cba这种写法在需要“带规则”的逆序时比纯切片灵活。2.3 实操场景回文判断、倒序输出与文件名校验逆序最常见的实战场景是回文判断def is_palindrome(s: str) - bool: clean .join(ch.lower() for ch in s if ch.isalnum()) return clean clean[::-1] print(is_palindrome(A man, a plan, a canal: Panama)) # True这里先把非字母数字的字符去掉再统一转小写最后和逆序比较。三步全是字符串方法组合出来的没有任何循环判断。注意我用了isalnum()后面第 3 节会详细拆这个家族。还有一个场景是做“倒序编号”。比如订单号的规则是“年份 流水号”但外部系统要求输出倒序后的编号用于视觉混淆这时候s[::-1]直接搞定。再比如校验文件名是不是某个规范要求以特定后缀结尾结合startswith/endswith即可。这些操作看起来不起眼但在自动化脚本里几乎是每天都要用到的。3. 字符串排序与字符判断从“筛选合法字符”到“拼最大数”3.1 对字符串本身排序和对字符排序字符串排序通常有两种理解一种是把字符串里的字符按 ASCII/Unicode 码点排好序另一种是对一堆字符串按照某个规则排序。前者用内置sorted()就能做s python chars sorted(s) print(chars) # [h, n, o, p, t, y] print(.join(chars)) # hnotpysorted(python)返回的是字符列表不是字符串所以经常要补一个.join(...)。排序规则默认按 Unicode 码点也就是先排数字、再排大写字母、再排小写字母中文会按 Unicode 编码排。想按忽略大小写来排可以用keystr.lowerprint(.join(sorted(Python, keystr.lower))) # hnoPty注意这个输出是h n o P t y还是h n o p t y其实keystr.lower只影响排序的比较值原始字符不变。所以P会被排在小写p所在的位置但输出结果里仍然是P而不是p。这个细节很常见容易让人误以为排序后大小写被统一了。3.2 经典“拼数”题如何让多个数字字符串拼出最大数有一道很经典的算法题题目大意是给你若干非负整数组成的字符串要重新排列它们使得拼接出来的数最大。比如[3, 30, 34, 5, 9]正确结果是9534330。如果不假思索按字典序从大到小排会得到95343030就错了。关键在于字符串拼接的比较规则。两个数字字符串a和b应该比较a b和b a哪个拼接结果更大。如果ab ba说明a应该排在b前面。Python 里可以用functools.cmp_to_key来实现自定义比较from functools import cmp_to_key def largest_number(nums): strs list(map(str, nums)) def compare(a, b): if a b b a: return -1 elif a b b a: return 1 return 0 strs.sort(keycmp_to_key(compare)) result .join(strs) return result.lstrip(0) or 0 print(largest_number([3, 30, 34, 5, 9])) # 9534330这题能解决核心是理解了 Python 的sort默认按字典序排字符串而字典序对“数字字符串拼接”并不总是正确。跨界到业务里类似的需求也很多比如给一群编号字符串按某种业务规则人工排序需要自定义key或cmp_to_key。在实际代码里我建议尽量用key而不是cmp能说是key的规则就不用cmp_to_key性能更好也更简洁。3.3 判断字母、数字、空白isalnum/isalpha/isdigit 家族字符串的字符判断方法基本就是围绕这几个isalpha()字符串里所有字符都是字母且至少有一个字符。注意中文也算字母因为 Python 用的是 Unicode 属性判断不是 ASCII 范围。isdigit()所有字符都是十进制数字。123为 True-123为 False因为负号不是数字字符。isalnum()所有字符都是字母或数字。这是isalpha()或isdigit()的并集。isspace()所有字符都是空白空格、制表符、换行都算。isupper()/islower()判断大小写但注意它们只判断字母部分123A.isupper()为 True。istitle()判断是否标题风格也就是每个单词首字母大写。热搜词里有一条“java 判断字符串中是否不是字母和数字”在 Python 里的等价写法反而是先判断“是不是”再取反。真要筛掉非字母数字可以用列表推导s abc123_中-文!! legal [ch for ch in s if ch.isalnum()] print(.join(legal)) # abc123中文注意str.isalnum()中文字符也返回 True。如果想只保留 ASCII 字母数字也就是英文字母和阿拉伯数字可以用ord()做范围判断def is_ascii_alnum(ch): return (a ch z) or (A ch Z) or (0 ch 9)“判断字符串是否为驼峰”这个需求也可以基于这些方法组合。比如判断变量名是否符合 camelCase先排除数字开头再检查非首字符位置是否有大写字母而其他位置必须是小写或数字def is_camel_case(s: str) - bool: if not s or s[0].isdigit(): return False return s[0].islower() and all(ch.isalnum() for ch in s)这个函数不算特别严谨但作为快速校验是够用的。更大的价值在于展示一种思路字符串判断不要自己手写一堆 ASCII 范围判断优先用内置方法组合。3.4 字符串比较 和 is 千万别混用搜索“字符串比较是否相等”的人八成是被和is搞混过。Python 里比较的是内容is比较的是对象身份也就是内存地址是否相同。a hello b .join([h, e, l, l, o]) print(a b) # True内容一样 print(a is b) # False不是同一个对象但直接写两个相同的字符串常量时有时is也会返回 True这是 Python 的字符串驻留机制在起作用简单字符串常量会复用同一个对象。这个机制属于实现细节千万别在业务代码里依赖它。判断字符串相等永远用。如果要把比较规则扩展到“忽略大小写”用a.lower() b.lower()即可。如果还想忽略首尾空格先strip()再比较。更复杂的情况比如比较版本号1.10.0和1.9.0直接字符串比较会错因为1.10.0的字典序小于1.9.0。这时要按点分割后转数字再比较这就不是简单的等值判断了。4. 字符串分割、拼接与截取半结构化文本处理三板斧4.1 split、rsplit、splitlines 的参数细节split()恐怕是字符串处理里用得最多的方法但它有几点很容易被忽略。第一如果不传参数split()会按任意空白字符分割而且会把连续空白当成一个分隔符还会自动去掉首尾空白。比如 a b c .split()得到[a, b, c]。但如果传了分隔符比如split(,)那么每个逗号都会产生一个分割点空字符串也会保留。a,,b,.split(,)得到[a, , b, ]最后一个空串也在。很多新手在这里踩坑解析 CSV 时莫名其妙多了空字段。第二split()还支持maxsplit参数。比如要拆分“只拆第一刀”的场景line host:port:user host, rest line.split(:, 1) print(host) # host print(rest) # port:user第三对应有rsplit()从右边开始拆。最常见的使用是rsplit(., 1)把文件名和扩展名分开filename archive.tar.gz name, ext filename.rsplit(., 1) print(name) # archive.tar print(ext) # gz第三组是splitlines()专门按换行符切割文本它能正确处理\n、\r\n等换行形式。读取日志文件内容时比手动split(\n)更稳。4.2 截取前两位、后两位和按步长取样字符串截取对应“字符串截取前两位”这个搜索词Python 的切片非常直观s python print(s[:2]) # py前两位 print(s[-2:]) # on后两位 print(s[1:4]) # yth中间从下标1到3切片不越界报错是一个非常实用的特性。ab[:10]返回abab[10:]返回空串。所以当你拿不定字符串长度时切片比index()配合异常处理要安全得多。按步长取样也值得一提。s[::2]取偶数位s[1::2]取奇数位这是很多算法题的基础。配合前面逆序的s[::-1]切片这个语法基本掌握了。4.3 join 拼接为什么比 高效字符串拼接大概是性能上被讨论最多的话题。看起来方便result for item in items: result item但每次都创建一个新字符串对象同时释放旧对象。当items数量很大比如上万条记录反复创建和回收对象的开销非常明显。用列表收集再一次性join是最稳妥的result .join(items)join()只会遍历一遍序列在内部一次性分配好足够的内存把各部分拼进去然后返回最终字符串。为什么列表先拼好再join比直接好可以类比搬砖就像每搬一块砖就重新建一栋房子join则是先把砖都数清楚再盖一栋完整的房子。不过拼接在数量很少时完全没问题比如两个字段名拼一个变量没必要强行join。代码可读性优先。4.4 实例解析一行带逗号的日志文本把前面这些串起来做一个实际任务解析一行类似2025-06-01,INFO,订单#A123,耗时 230ms的日志要求提取出订单号后面的数字部分并生成一个拼接后的标准格式。line 2025-06-01,INFO,订单#A123,耗时 230ms parts line.split(,) print(len(parts)) # 4 order parts[2] # 订单#A123 order_id .join(ch for ch in order if ch.isdigit()) print(order_id) # A123 - 这里输出 A123?等等我上面这个写法会得到A123还是123isdigit()只保留数字所以结果是123。如果还要把字母也留下用isalnum()clean_order .join(ch for ch in order if ch.isalnum()) print(clean_order) # A123像这种“从半结构化文本里抠出关键字段”的场景字符串方法组合几乎就是万能工具。先split切字段再配合字符判断过滤最后用格式化输出组装结果。5. 字符串与数字转换int()、str() 与大小写转换那些坑5.1 字符串转数字int/float 与进制参数int()能把数字字符串转成整数它有一个不那么起眼的参数base。默认base10传入其他进制时字符串同样合法print(int(1010, 2)) # 10 print(int(ff, 16)) # 255 print(int(0x10, 0)) # 16base 为 0 时会按字符串前缀识别进制这个功能在处理十六进制颜色值、二进制标志位时很管用。float()则用于带小数的字符串print(float(3.14)) # 3.14 print(float( -2.5 )) # -2.5自动去首尾空白真正容易出问题的是包含其他内容的字符串。int(1,000)会抛ValueError因为逗号不是数字。int(3.14)也会抛异常想从“3.14”得到整数 3得先float(3.14)再强转。还有百分号字符串float(85%)同样报错需要先把%去掉。5.2 安全转换的封装写法在业务脚本里我通常不建议直接用裸的int()因为一旦字符串来自用户输入、配置文件或爬虫数据你根本无法保证格式。封装一个带默认值的转换函数更实用def to_int(s, default0): try: return int(str(s).strip()) except (ValueError, TypeError): return default print(to_int(42)) # 42 print(to_int( )) # 0 print(to_int(None)) # 0 print(to_int(3.14)) # 0int(3.14) 会抛异常如果你希望遇到小数也能转先试floatdef to_number(s, default0): try: return int(str(s).strip()) except ValueError: try: return float(str(s).strip()) except ValueError: return default有些教程喜欢先调用isdigit()再转数字但这个方法并不适用于带负号和小数的字符串。比如-5.isdigit()是 False3.14.isdigit()也是 False。先判断再转换看似安全其实埋了雷直接 try/except 才是最稳的。5.3 数字转字符串与格式化精度数字转字符串一般直接用str()print(str(3.14)) # 3.14 print(str(100)) # 100需要控制小数位数时用format()或 f-string 更好x 3.1415926 print(f{x:.2f}) # 3.14 print(f{x:.0f}) # 3注意是四舍五入 print(f{x:,}) # 3.14159?上面最后一个例子不对。f{x:,}是给整数加千分位对浮点数也能加但x本身是小数的话会输出3.14159没有千分位分隔符。对一个大整数试一下y 1234567 print(f{y:,}) # 1,234,567至于.2f这种格式控制是字符串格式化的核心应用后面第 6 节还会展开。5.4 大小写转换方法对照lower、upper、casefold、title字符串大小写转换家族中日常用最多的是lower()和upper()。注意它们对中文无效但不会报错会原样返回。casefold()是lower()的加强版专门处理一些语言里的大小写折叠规则比如德语ß。如果你要在不区分大小写的场景下比较字符串用casefold()比lower()更严谨a STRASSE b straße print(a.lower() b.lower()) # False print(a.casefold() b.casefold()) # Truetitle()把每个单词的首字母大写看起来方便但对缩写、数字、带撇号的单词可能不合预期。dont.title()得到DonT标点后面的字母也被大写了。有这种需求时先想清楚到底要的是什么格式再挑工具。还有capitalize()只让第一个字母大写swapcase()会把大小写互换用得相对少。5.5 len() 的字符长度与字节长度len(你好)返回 2因为 Python 字符串的len()数的是字符个数不是字节数。如果你要把字符串写到网络字节流或者算存储字节数要用len(s.encode(utf-8))中文在 UTF-8 下一个字符占 3 个字节s 你好 print(len(s)) # 2 print(len(s.encode(utf-8))) # 6这个差异在写协议报文、数据库字段长度校验时特别容易踩坑。数据库里varchar(255)如果是按字节算中文内容可能提前触顶。字符串长度主题下还常遇到sys.getsizeof(s)的问题这个返回的是字符串对象本身占用的内存包含对象头等开销和逻辑字符长度两码事别混用。6. 模板字符串与格式化输出f-string、format 与 Template6.1 f-string最常用的格式化内联方式Python 3.6 之后f-string 几乎成了格式化的默认首选。把变量名或表达式直接放进花括号name Tom score 92.5 print(f{name} scored {score} points) print(f{name} scored {score:.1f} points) # Tom scored 92.5 pointsf{score:.1f}这类格式说明符在:后面直接写格式就行。对齐也是常用功能比如输出表格print(f{name:10}{score:6}) print(f{Tom:10}{92.5:6.1f})表示左对齐表示右对齐后面跟宽度。这在打印日志、生成报表对齐列时特别有用。还别忘了转换标志!r和!s。f{item!r}等价于repr(item)能把字符串带上引号调试时能看到隐藏字符这是我在排查字符串问题时最常用的一个技巧。6.2 format 与 % 老式格式化的适用场景老式%格式化print(%s scored %.1f points % (name, score))它最大的问题是花括号和类型混在一串字符串里参数多了容易错位可读性也比较差。但%在日志库logging里仍是推荐的写法因为日志模块支持延迟格式化传参%s时不会立刻开销只在需要输出时才调用str()。str.format()比%灵活支持位置参数和命名参数print({name} scored {score:.1f} points.format(namename, scorescore))当你把格式化模板存成配置字符串时用str.format()是合理的。比如template {date} {level} - {message} line template.format(date2025-06-01, levelINFO, messagedone)6.3 string.Template 模板字符串适合配置和用户模板string.Template是更安全的一种模板机制。它不使用花括号而是$name或${name}from string import Template t Template($name scored $score points) print(t.substitute(nameTom, score92.5))substitute缺参数会抛KeyError如果想在模板里允许缺失字段用safe_substitute它会把缺失的$name原样保留。这个特性在做用户自定义提示语时很实用比如一段告警模板里某些字段可能为空直接不传也不会让程序崩溃。为什么推荐这个而不是 f-string因为 f-string 是运行时立刻求值做不到“把模板和值分离”。业务上如果需要用户通过配置文件的$name形式来定义消息模板Template是最直观的选择。6.4 格式化常见的三个翻车点第一个翻车点是 f-string 里嵌套引号。字典的 key 要这样写info {name: Tom} print(f{info[name]}) # 花括号里用单引号外层字符串用双引号如果外层也想用单引号就得改用变量引用或者**解包别硬撑。第二个翻车点是花括号本身。模板里要输出 JSON 样例时{}在 f-string 里会被当成表达式边界必须写双花括号转义print(f{{a: 1}}) # {a: 1}第三个翻车点是f{score:,}这类千分位格式只对数字有效如果格式化一个字符串变量有一个不太常见的第三个参数。它支持嵌套比如根据条件选宽度width 10 name Tom print(f{name:{width}}) # 右对齐宽度10嵌套时把width放在花括号里实际格式化时才取值。这个技巧在画动态表格时很实用但大多数人不知道。7. 跨语言对比从 C#/Java/C 转来的人怎么快速切换7.1 逆序、判断字符类型、转 ASCII 的对照表搜索热词里有大量“字符串逆序输出c”“c#将字符串转成ascii码”“cstring字符串结束符”说明不少读者是从其他语言转过来的。Python 的写法往往最短但概念上要重新对齐。我把几个高频操作列成对照表需求PythonC#JavaC字符串逆序s[::-1]new string(s.Reverse().ToArray())new StringBuilder(s).reverse().toString()std::reverse(s.begin(), s.end())判断字母或数字ch.isalnum()char.IsLetterOrDigit(ch)Character.isLetterOrDigit(ch)std::isalnum(ch)转 ASCII 码ord(ch)(int)ch(int)chint(ch)ASCII 转字符chr(65)(char)65(char)65char(65)判断长度len(s)s.Lengths.length()s.length()/s.size()字符串相等s1 s2s1 s2s1.equals(s2)s1 s2Cstd::stringC# 和 Java 的字符串也是不可变对象这一点和 Python 一致。Java 中字符串比较一定要用equals()而不能用因为比较的是引用地址这个细节和 Python 里is的陷阱很像很容易让从 Java 转来的人踩坑。反倒 C 里的std::string是可变对象s[0] H是合法的从 C 过来的人用 Python 时特别容易撞上TypeError。7.2 结束符与长度C/C 的 \0 和 Python 的 len()C 风格的字符串本质是char数组以\0作为结束标记strlen靠遍历到\0才能算出长度。这意味着 C 字符串无法存储中间的\0字符一旦数据里混入空字节字符串就被截断。Python 字符串内部有明确长度信息不需要结束符len(s)在 O(1) 时间内返回。同时Python 字符串里可以包含\x00长度也照常计算。所以从 C/C 转过来的人不要再想着“我是不是要手动管理字符串结束符”Python 把这层复杂度彻底干掉了。Python 唯一靠近 C 层概念的场景是操作bytes类型。如果你拿到的是bytes对象比如网络包数据b\x00会真实存在且len()计算的是字节数。需要把字节解成字符串要用decode()这两个类型的关系很像 C 里的char*和字符串语义的区别。7.3 拼接性能StringBuilder、join 与字符串池Java 和 C# 里拼字符串都用StringBuilder核心原因和 Python 里不用一样字符串不可变反复拼接会产生大量中间对象。Python 对应的高效方案是列表 join思路完全一致只是写法不同。C# 开发者可能会习惯写var sb new StringBuilder(); foreach (var item in items) sb.Append(item); var result sb.ToString();Python 则是result .join(items)Java 的StringBuilder也类似。另外 Java 有字符串常量池Python 有小字符串驻留机制这些都是细节但都服务于同一个目标让频繁使用的相同字符串对象可以被复用节省内存。7.4 跨语言迁移时的常见认知冲突从其他语言转 Python最常见的认知冲突就是“字符串到底能不能改”。C 的std::string能改Java 和 C# 的字符串不能改但都有StringBuilder/StringBuffer做缓冲。Python 对应物是列表 join但更简单因为列表可以随意拼和删最后再一次性变回字符串。还有char和单字符字符串的区别。C# / Java 里单引号包的是char类型双引号包的是string类型类型不同不能互操作时不能直接拼。Python 里没有独立的字符类型单引号双引号都能定义字符串a b和ab没有区别。这点老手也常提提醒新手别以为a和a是两种类型。8. 常见问题与排查技巧实录8.1 中文和编码len 不是字节数写入文件要指定编码处理中文文本时最常被坑的就是编码。len(你好)是 2没问题但如果用len(你好.encode(utf-8))结果是 6。如果你用你好.encode(gbk)结果是 4。同一个字符串在不同编码下占用字节不同写文件时如果不指定编码Windows 上默认可能是 gbkLinux 上默认是 utf-8一到另一台机器就乱码。我的习惯是所有读写文本文件都显式指定encodingutf-8with open(data.txt, r, encodingutf-8) as f: content f.read()不要依赖系统默认编码。虽然 Python 3 里字符串本身是 Unicode 类型但文件读写、网络传输时编码必须打开你的“安全开关”这一步省不得。8.2 空串、None 和纯空白字符串的判断顺序判断字符串是否为空注意None不是字符串。if s:只能判断空串if s is None:才能判断空值空白字符串 在布尔判断里是 True因为它有内容。建议的顺序是先判断None再去空白def is_blank(s): return s is None or len(s.strip()) 0如果你只想确定“有可用的文本内容”这个函数已经覆盖了主要场景。如果不想引入strip()的额外复制还有一种更省内存的写法def is_blank(s): return s is None or not s.strip()实际用哪个都行代码简洁优先。8.3 不要靠“肉眼”判断字符串repr() 和 ord() 的妙用肉眼判断字符串太不可靠了。字符串里的不可见字符、零宽空格、全角空格打印出来都是空白但isspace()的结果、排序位置完全不同。调试时用repr()或者直接打印str.__repr__()的等价形式可以把转义序列和特殊字符显示出来s abc\tdef print(s) # abc def制表符直接顶出来了 print(repr(s)) # abc\tdef看到 \t 了如果要确认某个字符到底是什么码点用ord()print(ord( )) # 12288全角空格 print(ord( )) # 32半角空格排查文本对齐问题或解析爬虫页面时这个技巧能救你一命。以前我处理一个配置文件怎么 split 都多出奇怪字段后来repr()一看里面有零宽空格\u200b一下子找到了根源。8.4 用字符串方法还是正则先问自己三个问题日常处理字符串时很多人一上来就写正则但我一般是先问三个问题这个分隔符是否固定匹配模式是否能拆成几个简单的字符串子串性能是否重要如果分隔符固定split()永远比正则简单。如果只是检查开头结尾startswith()就够了。如果只是剔除首尾字符strip()足够。正则真正的用武之地是模式不确定的场景。比如从文本里匹配手机号、邮箱、日期格式re.findall()是标配。但在写正则之前先想想字符串方法能不能完成 80% 的需求这样代码的可读性和维护性会好很多。如果必须用正则建议给关键模式加注释一个月后回来看代码不至于想骂人。8.5 高频报错速查从 TypeError 到 ValueError最后整理一张速查表覆盖我在实际操作中见过的几个高频异常场景常见报错原因解决办法修改字符串单个字符TypeError: str object does not support item assignment字符串不可变改用切片或replace()生成新字符串int(1,000)ValueError: invalid literal for int()字符串包含千分位逗号先去掉逗号再转int(s.replace(,, ))split()ValueError: empty separator分隔符不能为空串用list(s)拆成字符列表f{info[name]}用了单引号嵌套SyntaxError花括号里引号和外层字符串冲突外层用双引号内层用单引号s.translate(None)想删字符TypeErrortranslate参数是映射表不是None用str.translate(str.maketrans(, , chars))或列表推导3.14.isdigit()是 False 却以为能转 int逻辑错误isdigit()对小数和负号返回 False用 try/except 转换这些错误我自己都踩过不止一次。字符串相关的坑往往不是语法不会而是“看起来很简单的特性背后藏着规则”。遇到异常别慌先repr()看真实内容再分段打印定位通常很快能解决。写到这里我再分享一个我最近顺手用的小脚本统计一段文本里中文、英文、数字、空白和其他符号各占多少字符。它的核心就是遍历字符串后调用isalpha()、isdigit()、isspace()组合判断。类似这种小工具体现的正是字符串方法的价值看懂上面的内容你完全可以照着这个思路解决自己的文本处理问题。
阅读完成 · 觉得有帮助?