首页 新闻 会员 周边 捐助

编码与序列化

0
[已关闭问题] 关闭于 2025-08-29 22:37

—把所有文字放进同一张码表,给每个字符发一张全球唯一的身份证(码位)。项目取名 Unicode,就是“Unique, Universal, and Uniform character encoding”的缩写

码位 = 历史继承 + 国家编码整体搬迁 + 公开提案投票 + 区段规划形成的,不是随机;码位就是一个整数编号,每个字符都是固定死的,“二进制”只在“编码阶段”才出现,二进制字节序列是“编码规则”的产物,Unicode 本身 不规定任何字节长度,“中”之所以在 UTF-8 里用 3 个字节,是因为它被 UTF-8 这套算法 编码成了 24 位,而 不是 Unicode 表给它预留了 24 位

  • 内存里的 Python str(也就是 JSON 字符串)是 一串 Unicode 码位(逻辑字符)。
  • 因为 还没有 调用 .encode(),所以它 还没变成 任何具体的字节序列(UTF-8、UTF-16、GBK……)。
  • 因此在这个阶段,只谈“Unicode 字符”而暂时不谈“编码”

Unicode 字符(实际码位) → 编码 → 字节序列
字节序列 → 解码 → Unicode 字符(实际码位)

s = ""          # 内存里:Unicode 码位 U+4E2D
b = s.encode('utf-8')   # 编码 → 字节序列 b'\xe4\xb8\xad'
s2 = b.decode('utf-8')  # 解码 → 又得到 Unicode ""

一句话
Unicode 字符本身只是一串“逻辑编号”;
编码/解码时,你告诉程序“按哪套规则把编号变成字节/把字节变回编号”
这套规则就是 UTF-8、UTF-16、GBK ……

 
表格
复制
维度举例说给谁听
字符集 Unicode / UCS 给“字符”发身份证
编码方案 UTF-8 / UTF-16 / UTF-32 / GBK 把身份证变成字节
运行时实现 PEP 393(Latin-1/UCS-2/UCS-4 切换) Python 解释器 内部 为了省内存
  • Unicode = UCS  叫法不同而已
UCS-2/UCS-4 和UTF-8/UTF-16/UTF-32都是编码规则,都是“把 Unicode 码位变成字节序列”的编码规则,分属两套体系,只是 UCS-2/4 属于 ISO 文档,UTF-8/16/32 属于 Unicode 联盟文档,今天实际使用几乎全是 UTF 系列
在内存里存字符串” 并不是 把一串 Unicode 码位直接平铺那么简单,解释器为了 节省内存加速索引方便 C 扩展,必须在码位之上再做一层“物理布局”——这就是各种内部编码规则PEP 393(Latin-1/UCS-2/UCS-4 切换)
  1. 字符集(抽象层)
    只有一套:Unicode / UCS —— 给每个字符发“身份证”(码位)。
  2. 内存布局(实现层)
    Python 解释器为了省内存,用 Latin-1 / UCS-2 / UCS-4 三种 物理存储格式 来放这些码位,但 它们都属于 Unicode 字符集,只是“怎么摆字节”不同。
  3. 对外 API(接口层)
    Python 把 任何物理格式封装成同一个 str 类型
    你在代码里永远只看见 Unicode 字符串
    永远不用管底下是 1 字节、2 字节还是 4 字节。
所以:
  • 字符集:统一叫 Unicode / UCS
  • 内存编码:内部三选一(Latin-1 / UCS-2 / UCS-4)
  • 用户接口:统一叫 str(Unicode 字符串)
 
_java_python的主页 _java_python | 小虾三级 | 园豆:984
提问于:2025-08-29 22:30
< >
分享
所有回答(1)
0

序列化:

“序列化”最终就是把对象在内存中的 状态(通常是 __dict__ 里的键值对)
映射成纯文本(JSON 字符串)。
下面用一句话把整个过程串起来,便于记忆:
  1. 对象 → 取 公开属性(__dict__
  2. 属性值 → 递归拆成 基本类型(str、int、list、dict…)
  3. 基本类型 → 按 JSON 语法 → 纯文本
  • __dict__ 里可能混有 datetime、Decimal、嵌套对象 —— 需要 钩子函数 或 类型判断 把它们“洗”成可 JSON 化的东西。
  • 方法、私有属性、文件句柄等 不会也不能 进 JSON。
  • “序列化” ≈ 把 __dict__ 洗干净后变成 JSON,但“洗”这一步才是工作量所在。
    • json 模块:跟我们自己手写一样,完全不保存方法、闭包、作用域——它只认“可 JSON 化的数据”。
    • pickle / marshal:这才是 Python 真正的“二进制序列化”库,它们会保存字节码、闭包、模块引用,但不再是文本,且跨版本/跨解释器有风险。
      • json(文本序列化)
        官方文档明确:
        “JSON can represent subsets of Python built-in types: dict, list, str, int, float, bool, None.”
        任何 function、method、lambda、code object、frame、generator、file、socket 都会直接 raise TypeError。
        对自定义类,只认 __dict__(或你提供的 default 钩子),不保存方法、闭包、局部变量。
        import json
        def foo(): pass
        json.dumps(foo) # TypeError: Object of type function is not JSON serializable


        pickle(二进制序列化)
        • 会保存函数、类、闭包、模块名、字节码、全局变量引用。
        • 反序列化时会重新 import 模块、重建闭包、绑定全局变量,因此能完整还原运行状态。
        • 代价:
          • 不是人类可读文本;
          • 只能由 相同 Python 版本、相同库环境 反序列化;
          • 存在安全风险(可执行代码)。
        • 想要 纯文本、跨语言、跨版本——用 json,就得接受“只存数据,不存代码”。
        • 想要 连函数、闭包、运行时状态一起打包——用 pickle,但要接受二进制和兼容性限制。
        在“把对象变成 JSON 文本”的语境下,方法、闭包、局部作用域 本来就 不应该、也无法 被序列化。

        为什么方法和闭包不序列化?
        1. 方法只是代码
          Python 里的函数、方法都是 代码对象(PyCodeObject),包含字节码、常量表、局部变量名……
          这些依赖于 解释器运行时环境(文件路径、依赖库、全局变量等)。
          把字节码塞到 JSON 里,别的进程甚至同一台机子下一次启动都还原不了。
        2. 闭包本质是“代码 + 捕获的外部变量”
          闭包捕获的变量可能在 栈帧 或 模块全局空间,它们的生命周期、地址在运行结束后就失效。
          想完整保存闭包,需要把 整段代码 + 外部变量快照 都打包——这已经超出 JSON 的范畴,进入 pickle / cloudpickle 这类二进制序列化领域。
        3. JSON 设计目标就是“数据”
          JSON 只支持:
          • 基本类型(str、int、float、bool、null)
          • 数组 []
          • 对象 {}
            它天生不包含“可执行代码”语义。
            因此方法、闭包、生成器、文件句柄、锁、socket 等 运行时资源 都 不可 JSON 化。
          • 如果你确实需要“连代码一起带走”
            • 用 pickle / cloudpickle / dill(二进制)
              它们能把函数、闭包、类、模块引用打包,但不再可读,且 版本/环境敏感。
              • 手写 JSON 序列化器。
                import json
                from datetime import datetime
                from decimal import Decimal
                
                def default(obj, _seen=None):
                    if _seen is None:
                        _seen = set()
                    oid = id(obj)
                    if oid in _seen:                       # 防循环
                        return f"<CircularRef:{type(obj).__name__}>"
                    _seen.add(oid)
                
                    # 1) 基本可 JSON 类型
                    if isinstance(obj, (str, int, float, bool)) or obj is None:
                        return obj
                
                    # 2) 日期 / Decimal 等常用扩展
                    if isinstance(obj, datetime):
                        return obj.isoformat()
                    if isinstance(obj, Decimal):
                        return float(obj)
                
                    # 3) list / tuple → 递归列表
                    if isinstance(obj, (list, tuple)):
                        return [default(x, _seen) for x in obj]
                
                    # 4) dict → 递归键值
                    if isinstance(obj, dict):
                        return {default(k, _seen): default(v, _seen) for k, v in obj.items()}
                
                    # 5) 其它任何对象 → 反射取公开字段
                    #    过滤掉私有属性(以 _ 开头)和方法
                    fields = {
                        k: default(v, _seen)
                        for k, v in vars(obj).items()
                        if not (k.startswith("_") or callable(v))
                    }
                    # 顺便把类名放进去,方便调试
                    fields["__class__"] = type(obj).__name__
                    return fields
                
                
                # ------------------ 测试 ------------------
                class Address:
                    def __init__(self, city, street):
                        self.city, self.street = city, street
                
                class User:
                    def __init__(self, id_, name, addr, score):
                        self.id = id_
                        self.name = name
                        self.addr = addr
                        self.score = Decimal(score)
                        self.created = datetime.now()
                
                addr = Address("北京", "长安街")
                user = User(1, "张三", addr, "99.5")
                
                print(json.dumps(user, default=default, ensure_ascii=False, indent=2))
                
                核心思路
                反射拿到实例的所有 公开字段(vars(obj) 或 __dict__)。
                递归时:
                遇到 基本类型 → 直接返回
                遇到 list / dict / tuple → 继续递归
                遇到 自定义类实例 → 再次反射它的 __dict__
                防循环引用 用 id(obj) 集合。
                遇到无法 JSON 化的类型(datetime、Decimal…)→ 统一钩子处理。

                 

_java_python | 园豆:984 (小虾三级) | 2025-08-29 22:37
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册