Python技术迷

Python 中 join() 和 split() 函数有什么区别?

TypeError: sequence item 2: expected str instance, int found

这类报错,我看 Python 代码时见得不少。十次里面有八次,是把 join() 当成“万能拼接器”用了。还有一种更隐蔽,日志切字段时一把 split() 下去,结果字段数量不对,后面入库全错位。

join() 和 split() 看着像一对反向操作,但别真把它们当成完全互逆。它俩一个负责“合起来”,一个负责“拆开”,方向不一样,脾气也不一样。

split() 是字符串的方法。

它盯着一个字符串,把它按指定分隔符拆成列表。

line = "2026-05-10|pay|u_1024|39.90"

parts = line.split("|")

print(parts)
# ['2026-05-10', 'pay', 'u_1024', '39.90']

这个没什么玄学,line 是原始字符串,"|" 是分隔符,拆完以后返回一个 list。

我平时处理日志、导出文件、回放接口数据,经常第一步就是这么干。但这里有个坑,别顺手写成这样:

line = "2026-05-10|pay|u_1024|39.90|extra"

day, biz, user_id, amount = line.split("|")

这段代码在线上数据稍微脏一点就炸。

ValueError: too many values to unpack

更稳一点的写法是限制拆分次数。日志里面有些字段本身可能带分隔符,这种情况别全量 split。

line = "2026-05-10|pay|u_1024|39.90|remark=a|b|c"

day, biz, user_id, rest = line.split("|", 3)

print(day)
print(biz)
print(user_id)
print(rest)
# 2026-05-10
# pay
# u_1024
# 39.90|remark=a|b|c

这里的 3 不是返回 3 段,而是最多切 3 刀。

这个细节我建议记住,比背概念有用。很多配置行、日志行、消息体,前几个字段固定,后面一坨是扩展内容,用 split("|", 3) 比无脑全切舒服很多。

再看 join()。

join() 也是字符串的方法,但它不是对字符串做拆分,而是把一个“字符串序列”按某个分隔符拼起来。

fields = ["2026-05-10", "pay", "u_1024", "39.90"]

line = "|".join(fields)

print(line)
# 2026-05-10|pay|u_1024|39.90

注意这个写法有点反直觉。

不是 fields.join("|"),而是 "|".join(fields)。

这个地方新手很容易别扭。我当年也嫌弃过这个设计,但写久了你会发现它很统一:谁当分隔符,谁来调用 join()。

比如逗号拼接:

tags = ["python", "backend", "log"]

print(",".join(tags))
# python,backend,log

换行拼接:

rows = [
"order_id,user_id,amount",
"o_1001,u_1024,39.90",
"o_1002,u_2048,66.00",
]

csv_text = "\n".join(rows)

print(csv_text)

这里 "\n" 就是分隔符。

但 join() 有一个很硬的要求:被拼接的每一项都必须是字符串。

下面这种代码,迟早报错:

order = ["o_1001", "u_1024", 39.90]

print("|".join(order))

报错:

TypeError: sequence item 2: expected str instance, float found

别怪 Python 小气。join() 不会帮你偷偷转类型,因为偷偷转类型有时候会埋坑。

我一般会明确处理一次:

order = {
"order_id": "o_1001",
"user_id": "u_1024",
"amount": 39.90,
"status": None,
}

fields = [
    order["order_id"],
    order["user_id"],
f"{order['amount']:.2f}",
    order["status"] or"",
]

line = "|".join(fields)

print(line)
# o_1001|u_1024|39.90|

这段代码看着啰嗦一点,但它把金额格式、空值兜底都写清楚了。数据导出这种东西,我宁可多写两行,也不愿意让 str(None) 混进文件里。

split() 还有一个容易误判的点:不传参数时,它不是按一个空格拆,而是按“任意空白”拆,并且会自动丢掉多余空白。

text = "  u_1024   pay   39.90  "

print(text.split())
# ['u_1024', 'pay', '39.90']

但你显式传空格,结果就不一样了:

text = "  u_1024   pay   39.90  "

print(text.split(" "))
# ['', '', 'u_1024', '', '', 'pay', '', '', '39.90', '', '']

这个地方我见过有人踩坑。处理命令行输出、人工整理的文本,用 split() 不传参经常更合适;处理严格分隔的数据,比如 CSV 的简化版、日志字段、协议字段,就应该明确写分隔符。

还有空字段的问题,也要留意。

line = "u_1024||39.90"

print(line.split("|"))
# ['u_1024', '', '39.90']

中间那个空字符串不是 bug,它表示两个 | 中间确实没有值。

这对导入数据很重要。你不能看到空字符串就随手过滤掉,不然字段位置会错。

比如下面这种写法,我一般不太信:

line = "u_1024||39.90"

fields = [x for x in line.split("|") if x]
print(fields)
# ['u_1024', '39.90']

看起来干净了,实际上把第二个字段弄没了。后面如果按下标取值,amount 可能就跑到别的字段上去了。

更稳一点:

line = "u_1024||39.90"

user_id, coupon_id, amount = line.split("|")

coupon_id = coupon_id orNone

print(user_id, coupon_id, amount)
# u_1024 None 39.90

这才像处理业务数据。

那 join() 和 split() 能不能互相还原?

简单场景可以。

raw = "python,java,go"

items = raw.split(",")
back = ",".join(items)

print(back)
# python,java,go

但只要数据里面本身包含分隔符,就不一定了。

items = ["python", "java,backend", "go"]

raw = ",".join(items)
print(raw)
# python,java,backend,go

print(raw.split(","))
# ['python', 'java', 'backend', 'go']

原来第二个元素是 "java,backend",拆回去以后变成两个字段了。

所以别把 join() + split() 当成可靠序列化方案。字段里可能出现分隔符时,该用 JSON 用 JSON,该用 CSV 模块用 CSV 模块,别自己硬拼。

import json

payload = {
"user_id": "u_1024",
"tags": ["python", "java,backend", "go"],
}

text = json.dumps(payload, ensure_ascii=False)
data = json.loads(text)

print(data["tags"])
# ['python', 'java,backend', 'go']

最后捋一下。

split():字符串拆列表。

"a|b|c".split("|")
# ['a', 'b', 'c']

join():列表拼字符串。

"|".join(["a", "b", "c"])
# 'a|b|c'

真正写代码时,我更关心这几个判断:

数据是不是严格分隔?字段里会不会带分隔符?空字段能不能丢?元素是不是全是字符串?金额、时间、None 要不要提前格式化?

这些问题想清楚了,join() 和 split() 基本不会写错。否则代码看着就一行,后面排数据错位能排半天。