你有没有遇到过这种情况:写了个接口,或者爬个网页,拿到一个 URL,想判断用户到底有没有传查询参数。结果用 urlsplit 一解析,不管人家是写了 https://x.com/search? 还是只写了 https://x.com/search,打印出来的 query 都是空字符串。你盯着屏幕愣了三秒:这到底是没传,还是传了但后面是空的?分不清啊。
这个坑在 Python 里藏了很多年。直到 3.15,urllib.parse 终于给了个干净的解法。今天说的就是它新增的 missing_as_none 参数,还有配套用来还原 URL 的 keep_empty。
老黄历:空字符串和 None 傻傻分不清
先看看以前的行为。在 Python 3.14 及更早版本,urlsplit 解析出来的每个字段,只要没值,一律给你一个空字符串。问题是,空字符串有两种完全不同的意思:一种是用户写了分隔符但没填内容,另一种是压根没写这个部分。这两种情况,旧版本给你的结果一模一样。
from urllib.parse import urlsplit
r1 = urlsplit("https://api.xxx.com/search?")
r2 = urlsplit("https://api.xxx.com/search")
print(r1.query) # 空字符串
print(r2.query) # 空字符串
# 都是空字符串,你分得清谁真没传吗?
看到没?r1 带了问号,r2 没带问号,可 query 全是空字符串。你想写个判断说用户没传参数就报错,对不起,判断不出来。只能自己拍脑袋猜,或者多写一堆啰嗦的正则去检查原始 URL 里有没有那个问号。
3.15 新参数 missing_as_none:缺了就是 None
Python 3.15 给 urlsplit、urlparse、urldefrag 都加了一个 keyword-only 参数 missing_as_none。一句话:凡是分隔符都没出现的字段,直接给你 None,而不是空字符串。这样空串和缺失就从根上分开了。
from urllib.parse import urlsplit
r1 = urlsplit("https://api.xxx.com/search?", missing_as_none=True)
r2 = urlsplit("https://api.xxx.com/search", missing_as_none=True)
print(repr(r1.query)) # 空字符串
print(repr(r2.query)) # None
# 有 ? 的是空串,没 ? 的是 None,一眼分清
这下逻辑清清楚楚:有问号的,query 是空字符串,确实传了只是后面空;连问号都没有的,query 是 None,压根没传。判断起来一行就够:
if r.query is None:
print("用户根本没传查询参数")
elif r.query == "":
print("用户传了 ? 但后面是空的")
else:
print("用户传了真实参数:", r.query)
keep_empty:拼回去的时候别把空串吞了
能解析,还得能还原。如果你用 missing_as_none=True 把 URL 拆开,改了点东西再拼回去,就要小心 urlunsplit 和 urlunparse 了。这两个函数在 3.15 也加了个 keep_empty 参数。
默认情况下,urlunsplit 会丢弃空字符串字段。但如果你拆的时候用了 missing_as_none=True,那拼回去时它会智能地默认 keep_empty=True,把空字符串保留成对应的分隔符,比如空 query 保留成问号,只丢掉真正的 None。要是你手动设 keep_empty=False,空串也会被一起吞掉。
from urllib.parse import urlsplit, urlunsplit
parts = urlsplit("https://docs.python.org?", missing_as_none=True)
# 此时 query 为空串 fragment 为 None
print(urlunsplit(parts))
# https://docs.python.org? (默认保留空 query 的 ?)
print(urlunsplit(parts, keep_empty=False))
# https://docs.python.org (空串被吞,? 也没了)
所以记住一条顺口溜:missing_as_none 负责拆得清,keep_empty 负责拼得回。两个搭配着用,URL 往返一遍不丢信息。
实战:写接口和爬虫怎么用
光说不练假把式。urlparse 同样支持这个参数,而且 params、query、fragment 这些字段统统受影响:
from urllib.parse import urlparse
u = urlparse("https://example.com/path", missing_as_none=True)
print(u.params) # None 以前是空串
print(u.query) # None
print(u.fragment) # None
再给你一个真实场景:做个小工具,要求调用方必须带查询参数,没带就直接报错。以前你得自己正则去抠原始字符串,现在一行搞定:
from urllib.parse import urlsplit
def need_query(url):
r = urlsplit(url, missing_as_none=True)
if r.query is None:
raise ValueError("URL 必须带 ?query= 参数")
return r.query
你看,以前要写一大坨判断逻辑,现在靠这个参数,代码干净得像刚擦过的玻璃。而且它不影响任何老代码,你不传这个参数,行为和以前完全一样,可以放心升级。
Python 3.15 正式版马上就要来了,现在已经进了 final beta,RC 也快了,这种小但真香的特性特别值得提前熟悉。下次再遇到 URL 解析,别再被空字符串和 None 绕晕了。
你有没有被 URL 解析或者 urllib.parse 坑过?比如拼接完 URL 多出个问号,或者少了个斜杠?在评论区聊聊,咱们一起排雷。