Python 字符串 split 与 join 是互为逆操作
在 Python 文本处理中,split() 负责把字符串按规则拆成列表,join() 负责把可迭代对象按分隔符拼回字符串。解析 CSV 行、拆分 URL 路径、拼接 SQL、格式化日志、生成 HTML 或路径,都会用到这两个操作。下面按方法、参数、典型场景、常见错误、性能和高级分割来整理。
一、split():默认空白与指定分隔符
不传参数时,split() 会把连续空白符当成一个分隔符,并自动去掉首尾空白;传入指定分隔符时,则按该分隔符严格拆分。
- text = 'Python Java Go Rust'
- print(text.split()) # ['Python', 'Java', 'Go', 'Rust']
- csv_line = '小明,25,北京,工程师'
- print(csv_line.split(',')) # ['小明', '25', '北京', '工程师']
- multiline = '第一行\n第二行\n第三行'
- print(multiline.split('\n')) # ['第一行', '第二行', '第三行']
复制代码
常见坑是 split() 和 split(' ') 的行为不同:前者合并连续空格,后者严格按每个空格拆分,连续空格会产生空字符串。
- text = 'a b c d'
- print(text.split()) # ['a', 'b', 'c', 'd']
- print(text.split(' ')) # ['a', '', '', 'b', '', 'c', '', '', '', 'd']
- text = ',a,b,c,'
- print(text.split(',')) # ['', 'a', 'b', 'c', '']
- print([x for x in text.split(',') if x]) # ['a', 'b', 'c']
复制代码
二、maxsplit 与 rsplit:限制拆分次数
maxsplit 用于限制拆分次数,rsplit 从右侧开始拆。解析键值对、HTTP 头、命令行参数时,通常只需要按第一个冒号或空格拆一次。
- text = 'a-b-c-d-e'
- print(text.split('-')) # ['a', 'b', 'c', 'd', 'e']
- print(text.split('-', 1)) # ['a', 'b-c-d-e']
- print(text.split('-', 2)) # ['a', 'b', 'c-d-e']
- print(text.rsplit('-', 2)) # ['a-b-c', 'd', 'e']
- header = 'Content-Type: application/json; charset=utf-8'
- key, value = header.split(':', 1)
- print(f'键: {key.strip()}') # 键: Content-Type
- print(f'值: {value.strip()}') # 值: application/json; charset=utf-8
复制代码
三、splitlines():更稳地按行拆分
splitlines() 能处理 \n、\r\n 等换行符,避免直接用 split('\n') 时残留 \r。keepends=True 可以保留行尾换行符。
- text = '第一行\n第二行\r\n第三行\n第四行'
- print(text.splitlines())
- # ['第一行', '第二行', '第三行', '第四行']
- print(text.splitlines(keepends=True))
- # ['第一行\n', '第二行\r\n', '第三行\n', '第四行']
复制代码
四、partition() 与 rpartition():一刀切三段
partition() 在第一次出现分隔符的位置把字符串切成三部分:分隔符之前、分隔符本身、分隔符之后。找不到分隔符时返回原字符串、空字符串、空字符串。rpartition() 则从右侧开始找。
- text = 'Python@Java@Go'
- print(text.partition('@')) # ('Python', '@', 'Java@Go')
- print(text.rpartition('@')) # ('Python@Java', '@', 'Go')
- print(text.partition('#')) # ('Python@Java@Go', '', '')
复制代码
只需要切一次且可能保留分隔符时,partition() 比 split() 更直接。例如解析 URL 协议、邮箱用户名和域名、MySQL 连接字符串。
- url = 'https://www.example.com/path?query=value'
- protocol, _, rest = url.partition('://')
- print(protocol) # https
- print(rest) # www.example.com/path?query=value
- email = 'user@example.com'
- local_part, _, domain = email.partition('@')
- print(f'用户名: {local_part}') # 用户名: user
- print(f'域名: {domain}') # 域名: example.com
- conn_str = 'mysql://user:password@localhost:3306/dbname'
- protocol, _, rest = conn_str.partition('://')
- auth, _, server_db = rest.rpartition('@')
- user, _, password = auth.partition(':')
- server, _, dbname = server_db.partition('/')
- print(f'用户: {user}, 密码: {password}, 服务器: {server}, 数据库: {dbname}')
复制代码
选择原则:需要切多次或次数不确定时用 split();确定只切一次时用 partition()。
五、join():把列表拼成字符串
join() 的调用方是分隔符,参数是可迭代对象。所有元素都必须是字符串,否则会抛出 TypeError。
- words = ['Python', 'Java', 'Go', 'Rust']
- print(', '.join(words)) # Python, Java, Go, Rust
- print(''.join(['a', 'b', 'c'])) # abc
- print('\n'.join(['第一行', '第二行', '第三行']))
- print('\t'.join(['姓名', '年龄', '城市']))
复制代码
非字符串元素需要先转换:
- items = ['项目', 1, '测试', 2]
- # '-'.join(items) # TypeError
- result = '-'.join(str(item) for item in items)
- print(result) # 项目-1-测试-2
复制代码
六、join() 的典型应用
拼接 SQL 占位符、生成 HTML、拼路径、输出表格都常用 join()。
- columns = ['name', 'age', 'city']
- placeholders = ', '.join(['%s'] * len(columns))
- query = f"INSERT INTO users ({', '.join(columns)}) VALUES ({placeholders})"
- print(query)
- # INSERT INTO users (name, age, city) VALUES (%s, %s, %s)
复制代码- items = ['苹果', '香蕉', '橘子']
- html_list = '<ul>\n' + '\n'.join(f' <li>{item}</li>' for item in items) + '\n</ul>'
- print(html_list)
- path_parts = ['home', 'user', 'documents', 'report.pdf']
- path = '/'.join(path_parts)
- print(path) # home/user/documents/report.pdf
复制代码- data = [
- ['小明', '25', '北京'],
- ['小红', '23', '上海'],
- ['小刚', '26', '广州'],
- ]
- for row in data:
- print('| ' + ' | '.join(row) + ' |')
复制代码
七、join() 为什么比循环 += 快
在循环中用 + 或 += 拼接大量字符串,会反复分配和复制内存;join() 通常只分配一次内存,因此在列表拼接场景更高效。
- import time
- words = ['hello'] * 100000
- start = time.perf_counter()
- result = ''
- for word in words:
- result += word
- elapsed_plus = time.perf_counter() - start
- print(f'用 + 拼接:{elapsed_plus:.4f}秒')
- start = time.perf_counter()
- result = ''.join(words)
- elapsed_join = time.perf_counter() - start
- print(f'用 join:{elapsed_join:.4f}秒')
复制代码
原文字段给出的结论是:时间差距可能达到几十倍甚至上百倍。实际项目中,少量拼接用 + 或 f-string 更简洁,大量拼接用列表收集后再 join。
八、split() 与 join() 的组合用法
格式转换、日期标准化、单词倒序都依赖先拆后拼。
- csv_data = '小明,25,北京,工程师'
- name, age, city, job = csv_data.split(',')
- sql = f"INSERT INTO users (name, age, city, job) VALUES ('{name}', {age}, '{city}', '{job}')"
- print(sql)
- date_slash = '2024/05/30'
- date_dash = '-'.join(date_slash.split('/'))
- print(date_dash) # 2024-05-30
- sentence = 'I love Python programming'
- words = sentence.split()
- reversed_sentence = ' '.join(reversed(words))
- print(reversed_sentence) # programming Python love I
复制代码
清理空白和生成 slug 也可以直接用 split() + join():
- def normalize_whitespace(text):
- return ' '.join(text.split())
- text = 'Hello world\t\tPython \n\n 编程'
- print(normalize_whitespace(text)) # Hello world Python 编程
- def slugify(text):
- slug = '-'.join(text.lower().split())
- return slug
- print(slugify('My First Blog Post')) # my-first-blog-post
复制代码
矩阵或表格转置同样可以用 split() 拆行、zip() 转置、join() 拼回:
- table = [
- '姓名,年龄,城市',
- '小明,25,北京',
- '小红,23,上海',
- '小刚,26,广州',
- ]
- rows = [row.split(',') for row in table]
- transposed = list(zip(*rows))
- result = [','.join(col) for col in transposed]
- for line in result:
- print(line)
复制代码
九、多分隔符分割与保留分隔符
Python 内置 split() 只支持单个分隔符。多分隔符可以用 replace 后再 split,也可以直接用正则 re.split。若正则中加捕获组,还能保留分隔符。
- import re
- text = '苹果,香蕉;橘子|葡萄 西瓜'
- result = text.replace(';', ',').replace('|', ',').replace(' ', ',').split(',')
- print(result)
- result = re.split(r'[,;| ]', text)
- print(result)
- result = re.split(r'([,;| ])', text)
- print(result)
复制代码
保留分隔符在分句场景很有用,例如按句末标点切分并保留标点:
- import re
- text = 'Hello, World! How are you?'
- result = re.split(r'([,.!? ])', text)
- result = [s for s in result if s and s != ' ']
- print(result)
- def split_sentences(text):
- parts = re.split(r'([。!?.!?])', text)
- sentences = []
- for i in range(0, len(parts) - 1, 2):
- sentences.append(parts[i] + parts[i + 1])
- if len(parts) % 2 == 1 and parts[-1]:
- sentences.append(parts[-1])
- return sentences
- text = '你好!今天天气不错。你吃饭了吗?'
- print(split_sentences(text))
复制代码
十、实战:手写 CSV 行解析与 csv 模块
标准 split(',') 不能处理引号内的逗号,例如 小明,"北京,朝阳区",25。手写解析器需要记录是否处于引号内,只在非引号状态下遇到分隔符才切分。实际项目更推荐直接用 csv 模块。
- def parse_csv_line(line, delimiter=',', quote_char='"'):
- fields = []
- current_field = ''
- in_quotes = False
- for char in line:
- if char == quote_char:
- in_quotes = not in_quotes
- elif char == delimiter and not in_quotes:
- fields.append(current_field)
- current_field = ''
- else:
- current_field += char
- fields.append(current_field)
- return fields
- csv_line = '小明,"北京,朝阳区",25,工程师'
- fields = parse_csv_line(csv_line)
- print(fields) # ['小明', '北京,朝阳区', '25', '工程师']
- import csv
- reader = csv.reader([csv_line])
- print(next(reader)) # ['小明', '北京,朝阳区', '25', '工程师']
复制代码
十一、性能与最佳实践
只需要切一次时,partition() 找到第一个分隔符就停止,比 split() 更合适;需要切多次时用 split()。
- text = 'key: value with many colons: extra data'
- key, value = text.split(':', 1)
- key, _, value = text.partition(':')
复制代码
拼接策略可以按数据量选择:
- name = '小明'
- greeting = '你好,' + name + '!'
- greeting = f'你好,{name}!'
- words = ['Python', 'Java', 'Go', 'Rust']
- result = ', '.join(words)
- lines = []
- for i in range(1000):
- lines.append(f'第{i}行')
- result = '\n'.join(lines)
复制代码
核心结论:split() 把字符串转列表,rsplit() 从右拆,splitlines() 按换行拆,partition() 和 rpartition() 返回三元组,join() 把列表拼成字符串。需要切多次用 split(),只切一次用 partition(),大量字符串拼接优先用 join(),多分隔符分割用 re.split()。split() 与 join() 互为逆操作,组合起来就能完成大量文本解析、清洗、格式转换和结构化输出任务。 |