接口查不到用户,日志里 user_code 明明传的是 AbC1029,库里存的是 abc1029。
这种问题我一般不先怀疑数据库,也不先怀疑 ORM。先把入参打出来,看大小写有没有被一路带进去。字符串转小写这个动作,看着像一行代码,线上脏数据一多,坑也不少。
Python 里最常用的就是 lower()。
raw_code = "AbC1029"
db_code = raw_code.lower()
print(db_code)
# abc1029
注意一点,lower() 不会改原来的字符串。Python 的字符串是不可变对象,它只是返回一个新的字符串。
name = "OrderStatus"
name.lower()
print(name)
# OrderStatus
这段代码我见过好几次,写的人以为已经转了,后面继续拿 name 去比对,结果该错还是错。
正确写法至少要接住返回值:
name = "OrderStatus"
name = name.lower()
print(name)
# orderstatus
如果只是普通英文、编号、接口字段,lower() 足够用了。
比如处理接口参数,我通常会顺手把空格一起干掉:
defclean_user_code(value: str) -> str:
if value isNone:
return""
return value.strip().lower()
code = clean_user_code(" USER_7788 ")
print(code)
# user_7788
这里的顺序别写反了。
先 strip(),再 lower()。虽然大多数时候反过来也没事,但字段清洗我习惯先处理边界字符,再处理内容本身。排查日志时也更容易判断到底是哪一步把数据改掉了。
还有一种写法,批量清洗列表:
user_codes = ["A001", " b002 ", "C003", "", "Admin"]
clean_codes = []
for code in user_codes:
code = code.strip().lower()
ifnot code:
continue
clean_codes.append(code)
print(clean_codes)
# ['a001', 'b002', 'c003', 'admin']
别一上来就写很花的列表推导式。字段清洗里经常要加空值判断、异常记录、黑名单过滤,写成几行反而更稳。
当然,真要简单处理,也可以这样:
user_codes = ["A001", " b002 ", "C003"]
clean_codes = [code.strip().lower() for code in user_codes]
print(clean_codes)
# ['a001', 'b002', 'c003']
再看一个容易忽略的地方:字典里的 key。
有些系统传参特别随意,一会儿 UserName,一会儿 username,还有人传 USER_NAME。如果后端按固定 key 取值,偶尔就会取不到。
payload = {
"UserName": "Tom",
"USER_CODE": "A7788",
"Source": "APP"
}
fixed_payload = {}
for key, value in payload.items():
fixed_payload[key.lower()] = value
print(fixed_payload)
# {'username': 'Tom', 'user_code': 'A7788', 'source': 'APP'}
这段代码不复杂,但很实用。尤其是对接第三方接口时,别太相信对方文档里的字段大小写。有些文档写得挺规整,实际请求一抓包,完全不是一回事。
不过 lower() 也不是所有场景都最合适。
如果涉及多语言字符,尤其是用户昵称、邮箱、本地化文本,可以看一下 casefold()。它比 lower() 更激进,适合做大小写无关的比较。
left = "Straße"
right = "STRASSE"
print(left.lower() == right.lower())
# False
print(left.casefold() == right.casefold())
# True
这地方我不会建议你所有代码都换成 casefold()。业务里如果只是英文状态码、订单号、渠道码,用 lower() 更直观。只有在“用户输入文本比较”这种场景,再考虑 casefold()。
比如登录名比较,可以这样写:
defsame_login_name(input_name: str, saved_name: str) -> bool:
if input_name isNoneor saved_name isNone:
returnFalse
return input_name.strip().casefold() == saved_name.strip().casefold()
print(same_login_name(" Admin ", "admin"))
# True
还有个坑,数字和符号不会被 lower() 影响。
text = "API_V2-USER_01"
print(text.lower())
# api_v2-user_01
所以别指望 lower() 帮你做格式校验。它只管大小写,不管字符串合不合法。
我一般会把“转小写”和“校验格式”分开写。混在一起,后面查问题很烦。
import re
defnormalize_channel(raw: str) -> str:
channel = raw.strip().lower()
ifnot re.fullmatch(r"[a-z0-9_]+", channel):
raise ValueError(f"bad channel: {raw!r}")
return channel
print(normalize_channel(" APP_STORE "))
# app_store
这里用 {raw!r} 是故意的。日志里能看到原始字符串有没有空格、换行符。有些问题肉眼看日志看不出来,加上 repr 一下就露馅。
Python 字符串转小写,日常记住这几个就够了:
str.lower():普通英文、编码、状态值,优先用它。
str.casefold():需要更严格做大小写无关比较时再用。
strip().lower():处理外部输入时更常见,别忘了接收返回值。
真正容易出问题的不是不会转小写,而是转了没赋值、只转了一边、清洗和校验混在一起。代码短归短,该留痕的地方还是要留,不然后面查日志只能靠猜。