功能说明
浏览器的表单、标签筛选和多选控件会产生同名多值参数,例如 tag=python&tag=go。直接使用 dict 解析会丢失前面的值,因此这里把每个键映射到值列表,保留原始顺序。多值映射还便于重建规范的查询串,用于分页链接、缓存键或签名前的参数归一化。
build_multimap 支持按键排序,保证同一组参数在不同调用中生成完全相同的字符串;get_one 和 get_bool 则在读取单值参数时提供显式校验,避免多值覆盖或误判真假值。
关键代码
parse_qsl 负责解码百分号转义并把加号还原为空格,keep_blank_values 让空字符串也保留,从而区分未传参数与传了空值。build_multimap 先按键排序,再按值顺序展开成键值对,最后交给 urlencode。get_one 在值多于一个时抛出 ValueError;get_bool 把常见真值和假值归一化,其他输入直接拒绝,而不是静默当作假。
使用注意
查询串长度有限制,超长多值参数应考虑改走 POST 或压缩。键名大小写敏感,需要与后端约定一致。空值会被解析为空字符串,业务上要明确它与缺失参数的区别。get_bool 不接受空字符串,若前端可能提交空值,应先做默认值处理。重建查询串会重新编码,原有参数的顺序和转义形式可能与输入不同,签名场景务必约定排序规则。
验证方式
示例查询包含两个 tag、一个 page、一个 debug 和一个空值参数。打印结果中 tag 对应 python 和 go 两项,page 为字符串 2,debug 解析为 True,empty 是空字符串而非 None。重建后的查询串按 debug、empty、page、tag 顺序排列,两个 tag 依次出现。对重复的 tag 调用 get_one 会输出 rejected 提示。
代码示例
from collections import defaultdict
from collections.abc import Iterable
from urllib.parse import parse_qsl, urlencode
Params = dict[str, list[str]]
def parse_multimap(query: str) -> Params:
grouped: dict[str, list[str]] = defaultdict(list)
for key, value in parse_qsl(query, keep_blank_values=True):
grouped[key].append(value)
return dict(grouped)
def build_multimap(params: dict[str, Iterable[str]], *, sort_keys: bool = True) -> str:
pairs: list[tuple[str, str]] = []
keys = sorted(params) if sort_keys else list(params)
for key in keys:
for value in params[key]:
pairs.append((key, value))
return urlencode(pairs)
def get_one(params: Params, key: str, default: str | None = None) -> str | None:
values = params.get(key)
if not values:
return default
if len(values) > 1:
raise ValueError(f"{key} must not repeat")
return values[0]
def get_bool(params: Params, key: str, default: bool = False) -> bool:
value = get_one(params, key)
if value is None:
return default
normalized = value.strip().lower()
if normalized in {"1", "true", "yes", "on"}:
return True
if normalized in {"0", "false", "no", "off"}:
return False
raise ValueError(f"{key} is not a boolean")
def main() -> None:
query = "tag=python&tag=go&page=2&debug=1&empty="
params = parse_multimap(query)
print(params)
print("tags:", params["tag"])
print("page:", get_one(params, "page"))
print("debug:", get_bool(params, "debug"))
print("empty:", repr(get_one(params, "empty")))
print(build_multimap(params))
try:
get_one({"tag": ["a", "b"]}, "tag")
except ValueError as error:
print("rejected:", error)
if __name__ == "__main__":
main()