Python urlencode 展开列表参数:doseq、空列表与字符串化要一起设计
为搜索服务生成请求时,一个筛选项可能选中多个标签。Python 字典里放一个列表很方便,但把字典直接交给 urlencode,并不会自动得到多个同名参数。是否展开由 doseq 控制;没有明确选择时,接收端可能读到列表的文字表示。
比编码更早的问题是值的含义:空列表代表不筛选,空字符串代表明确留空,None 代表没有提供,数字零则可能是合法页码。本文用一个小型适配层把这些约定变成字符串对,再把百分号编码交给标准库,便于逐层验证。
AI概念配图,非真实界面:以抽象物件说明本文主题,不代表运行结果。
先决定值,再让标准库编码
下面完整代码用 Python 3 运行。normalize 保留列表元素顺序,把数字显式转换成字符串,并为布尔值采用小写单词;None 被省略,空列表展开后没有参数。为了保持示例边界清楚,调用方应只传入字符串、数字、布尔值及这些值组成的列表。
代码同时保留直接使用 doseq 的断言,便于看出标准库负责哪一步。正式适配层返回二元组列表,因此重复名称不会被字典覆盖,还可以保持标签与其他筛选条件之间的顺序。列表项中的 None 同样按未提供处理,而空串会留下。重复项是否去重则属于另一条业务规则,不在这里擅自改变。
from urllib.parse import urlencode, parse_qsl, quote
def normalize(items):
pairs = []
for key, value in items:
values = value if isinstance(value, (list, tuple)) else [value]
for item in values:
if item is None:
continue
if isinstance(item, bool):
text = "true" if item else "false"
else:
text = str(item)
pairs.append((str(key), text))
return pairs
assert urlencode({"tag": ["red", "blue"]}, doseq=True) == "tag=red&tag=blue"
assert parse_qsl(urlencode({"tag": ["red", "blue"]})) == [
("tag", "['red', 'blue']")
]
pairs = normalize([
("tag", ["red", "blue"]), ("note", ""), ("page", 0),
("enabled", False), ("unused", None), ("empty", []),
("query", "a+b / c"),
])
encoded = urlencode(pairs)
assert parse_qsl(encoded, keep_blank_values=True) == pairs
assert ("note", "") not in parse_qsl(encoded)
assert "unused=" not in encoded and "empty=" not in encoded
percent = urlencode([("query", "a+b / c")], quote_via=quote, safe="")
assert percent == "query=a%2Bb%20%2F%20c"
try:
parse_qsl("a=1&b=2", max_num_fields=1)
except ValueError:
pass
else:
raise AssertionError("field limit ignored")
print(encoded)
print(percent)
print("normalization and round-trip checks passed")两种空值不能靠接收端猜
输出第一行包含两个 tag 参数、一个空 note 和零值 page,不包含 unused 与 empty。parse_qsl 开启 keep_blank_values 后,才能读回 note 的空字符串;默认行为会忽略这样的字段。往返验收必须和服务端真实的空值政策一致。
不要用 if value 统一删除空值,否则零和 False 也会丢失。示例以身份比较识别 None,并单独处理布尔类型,原因是 Python 的布尔值也是整数类型的一部分。先写出业务转换,可以避免后续维护者凭直觉扩大过滤范围。
当前文档提示,Python 3.14 起部分假值对象的直接接受行为已弃用。把数字和布尔值转换成明确的字符串,同时主动展开空列表,比依赖隐式行为更容易跨版本核查。这也让服务端拿到的单词形式成为接口约定,而非语言细节。
编码选项要与对端的格式约定一致
默认编码器将空格写成加号,而原本的加号会转义。指定 quote_via=quote 可以把空格写成百分号形式;示例显式使用 safe 空串,所以斜杠仍会编码。不要为了看起来简洁,把值里的分隔符随意加入安全字符集合。
这里传入的都是未编码原值。若先手动把空格改成百分号序列,再调用 urlencode,百分号本身还会被编码,接收端一次解码后就留下另一层转义。排查时分别打印规范化的键值对和最终字符串,比只盯着完整网址更容易发现重复处理。
最后一组断言限制解析字段数量,超过预算就报错。它只验证查询参数层,不验证主机名、路径或业务授权,也不会让任意输入自动安全。实际客户端应从可信基础地址组装请求,并把允许的参数名称与数量作为独立规则维护。


