Python中的Unicode转义序列

大家好,我是game1024,最近编写程序的时候经常碰到乱码的问题,中文死活显示不出来,以往都是直接使用encode,decode的一顿转换,最后虽然也解决了问题,但是并不明白其中的原理。对Unicode,UTF-8、GBK的概念也很模糊

最近又遇到了类似的问题,但是发现encode,decode大法不好使了,所以想彻底的弄清楚其中的原理

问题背景

问题背景是我在使用json进行序列化的时候,数据里面中文被转换成了\u5546\u54c1\u540d\u79f0这种格式的东东,一开始我以为是又碰到乱码问题了,但是在网上找了很多方法encode('utf-8') decode('utf-8')都不好使。

最后是在google上搜索,了解到json.dumps有一个ensure_ascii的参数,将这个参数设置成False就能解决

python 复制代码
data = json.dumps({
    'data':{
        'items': [
                     {
                         'id': '00000000',
                         'name': '商品名称',
                         'L1_category_name': '一级类目名称',
                         'L2_category_name': '二级类目名称',
                         'L3_category_name': '三级类目名称',
                         'ext_map': {
                             'score': 0.9123,
                             'dumps': 'search.xxxx.yyyy.zzzz'
                         }
                     }
                 ]*20
    },
    'traceId': '00000000000000000',
    'code':200
}, ensure_ascii=False)

print(data)

Unicode 转义序列

问题是解决了,可是还不够,我还想知道ensure_ascii参数到底做了什么,于是我问了一下豆包大模型,得到的回复是这样的

"json.dumps() 是 Python json 模块中的一个函数,可用于将 Python 对象序列化为 JSON 格式的字符串。当设置 ensure_ascii=True(默认值)时,非 ASCII 字符会被转换为 Unicode 转义序列。"

听下来的意思是,对于中文等一类的字符,当开启ensure_ascii=True,会把它转换成'\uxxxx'的格式

那么什么是unicode转义序列呢,带着问题我继续问大模型

大概意思就是\u后面那串xxxx的数字代表的是字符对应的unicode码值(十六进制)

带着好奇,我想做一下实验,比如"我"这个字符,经过unicode转义序列会变成什么,究竟后面对应的数字是不是unicode的码值

python 复制代码
print(json.dumps("我", ensure_ascii=True))

# 输出
"\u6211"

接着我去查了一下文档,在python里面可以通过ord()函数来获取一个字符的unicode码值

python 复制代码
print(ord('我'))

# 输出
25105

哎,怎么好像不太对,25105和6211不相等啊,等等!!!ord是返回的是十进制表达形式,\uxxxx是十六进制的表达形式,是不能直接拿来比较的,需要先把十进制的数转成十六进制的表达形式

python 复制代码
print(hex(ord('我')))

# 输出
0x6211

可以看到,确实"我"这个字符的转义序列的数字,和十六进制的unicode码值是可以对应上的

ensure_ascii的背后

我通过json.dumps("我", ensure_ascii=True)确实得到了"我"的unicode转义序列,不过我还想知道ensure_ascii这个参数背后到底做了什么,它是怎么把"我" => "\u6211"的

带着问题我继续问大模型,怎么将一个字符变换成unicode转义序列,得到的回答是可以利用str.encode("unicode_escape")。

python 复制代码
# 得到'我'经过unicode_escape转义编码后的字节数组
bs = '我'.encode("unicode_escape")
print(bs)
# 输出
b'\\u6211'

可以看到有两个\\这是因为python解释器想要表示\这个字符的时候,需要两个\\

我们可以通过另一种方式,更直观地得到字节数组里面有什么

python 复制代码
bs = '我'.encode("unicode_escape")
for byte in bs:
    # hex:byte对应的十六进制表达形式
    # chr: byte对应的字符
    print(hex(byte), chr(byte))

# 输出
0x5c \
0x75 u
0x36 6
0x32 2
0x31 1
0x31 1

可以看到里面就是'\u6211'这个字符串对应的unicode码值(对于英文字符来说,unicode码值和ascii码值是一样的)

如果我想要得到'\u6211'这个字符串,只需要将字节数组decode('ascii')就行

python 复制代码
bs = '我'.encode("unicode_escape")
print(bs.decode('ascii'))

# 输出
\u6211

结论

所以实际上ensure_ascii=True背后做的事情,与下面的代码等价

python 复制代码
"a json string".encode("unicode_escape").decode("ascii")

如果我想要把一个unicode转义字符串,转换成正常显示的中文可以这样操作

python 复制代码
literal = r'\u6211'.encode("ascii").decode("unicode_escape")
print(literal)

# 输出
好

# 因为unicode转义序列中的所有字符都是0~127码值的字符
# 所以每个字符的utf-8编码于ascii编码是一样的
# 下面这段代码依然可以得到正确结果
literal = r'\u6211'.encode('utf-8').decode('unicode_escape")
print(literal)

# 输出
好

附录

python 复制代码
bin() # 获取一个整型数字的二进制形式
hex() # 获取一个整型数字的十六进制形式
ord() # 获取一个字符的unicode码值
chr() # 获取一个整型数字对应的字符

'我'.encode('utf-8')          # 获取字符串经过utf-8编码后的字节数组
'我'.encode('unicode_escape') # 获取字符串经过unicode_escape编码后的字节数组

r'\u6211'.encode('ascii').decode('unicode_escape') # 获取'\u6211'转义序列对应的字符串
相关推荐
一条Go3 小时前
OnCall 时段路由:跨午夜、左闭右开与「Local 不是时区」
后端·架构·github
Moment3 小时前
不会后期的我,发现了一个开源胶片神器😍😍😍
前端·后端·开源
数据库小学妹3 小时前
pgbouncer 事务池:prepared statement、临时表与会话状态
数据库·后端·postgresql
三小河3 小时前
SWC 凭什么比 Babel 快 ?项目优化迁移踩坑记录
前端·后端
鶴哥只手遮天4 小时前
《Redis 8.10.3 深度剖析》系列 ·第 17 章. 一个迟到十六年的类型
redis·后端·开源
知守观4 小时前
同一个团队两套分包:DDD 四层落地后,domain 层最大的类还是 5885 行
java·后端·领域驱动设计
Anymous4 小时前
我手写的 Redis 锁,输给了 10 秒的 TTL
后端·架构
Rain的Java大神之路5 小时前
设计一套生产级 API 接口:从契约、安全、幂等到限流,我踩过的坑都在这了
java·后端·面试
SimonKing5 小时前
零配置也能白嫖前沿大模型!dsh-our-free-model插件真香警告
java·后端·程序员
nice_lcj5205 小时前
【从零理解 Spring AI · 15】让检索更准:先宽召回,再精排序
java·后端·rag·spring ai·ai应用开发·检索优化·重排序