杰瑞科技汇

Python protobuf 如何正确读取数据?

  1. 定义 .proto 文件:创建你的数据结构定义。
  2. 生成 Python 代码:使用 Protobuf 编译器 (protoc) 将 .proto 文件转换成 Python 类。
  3. 读取/解析数据:在 Python 代码中导入生成的模块,并解析二进制数据。

第 1 步:定义 .proto 文件

你需要一个 .proto 文件来描述你的数据结构,我们以一个简单的 Person 消息为例。

Python protobuf 如何正确读取数据?-图1
(图片来源网络,侵删)

创建一个名为 person.proto 的文件:

// person.proto
syntax = "proto3"; // 指定使用 Protocol Buffers 的第 3 版语法
// 定义包名,可以防止命名冲突
package tutorial;
// 定义一个 Person 消息类型
message Person {
  string name = 1;
  int32 id = 2;       // 独特的ID号
  string email = 3;
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }
  repeated PhoneNumber phones = 4; // 'repeated' 关键字表示这是一个数组/列表
}

关键点说明

  • syntax = "proto3";:必须指定,推荐使用 proto3
  • message:定义一个数据结构,类似于 Python 中的类或 C++ 中的结构体。
  • = 1, = 2:这些是字段编号,是 Protobuf 中非常重要的概念,在定义消息后,绝对不要修改这些编号,因为它会影响数据的序列化和反序列化。
  • repeated:表示该字段可以包含 0 个或多个值,在 Python 中会映射为 list
  • enum:枚举类型,在 Python 中会映射为 Enum 类。

第 2 步:生成 Python 代码

你需要使用 protoc 编译器将 person.proto 文件转换成 Python 代码。

前提条件:安装 protoc 和 Python Protobuf 库

  1. 安装 protoc 编译器

    Python protobuf 如何正确读取数据?-图2
    (图片来源网络,侵删)
    • macOS (使用 Homebrew):
      brew install protobuf
    • Windows (使用 Scoop):
      scoop install protobuf
    • Linux (Debian/Ubuntu):
      sudo apt-get update
      sudo apt-get install protobuf-compiler
    • 下载安装:也可以从 Protocol Buffers 官网 下载对应系统的预编译包。
  2. 安装 Python Protobuf 库: 在你的 Python 环境中,使用 pip 安装:

    pip install protobuf

运行编译器

打开终端或命令行,进入到 person.proto 文件所在的目录,然后执行以下命令:

# -I 指定 .proto 文件所在的目录(. 表示当前目录)
# --python_out 指定生成的 Python 代码的输出目录(. 表示当前目录)
protoc -I=. --python_out=. person.proto

执行成功后,你会发现目录下多了一个 person_pb2.py 文件,这就是我们需要的 Python 模块,它包含了 Person 消息类的定义。


第 3 步:在 Python 中读取/解析数据

现在我们可以在 Python 脚本中使用这个生成的模块了,读取操作通常指的是解析一个已经序列化好的二进制文件。

Python protobuf 如何正确读取数据?-图3
(图片来源网络,侵删)

假设我们有一个 person.bin 文件,里面存储了一个 Person 对象的二进制数据。

示例:创建并序列化数据(为了演示)

我们创建一个脚本 create_data.py 来生成这个二进制文件,这样我们后面才有数据可以读取。

# create_data.py
import person_pb2
# 1. 创建一个 Person 对象
person = person_pb2.Person()
person.name = "John Doe"
person.id = 123
person.email = "john.doe@example.com"
# 添加电话号码
phone1 = person.phones.add()
phone1.number = "123-456-7890"
phone1.type = person_pb2.Person.HOME
phone2 = person.phones.add()
phone2.number = "987-654-3210"
phone2.type = person_pb2.Person.WORK
# 2. 将对象序列化为二进制数据
serialized_data = person.SerializeToString()
# 3. 将二进制数据写入文件
with open("person.bin", "wb") as f:
    f.write(serialized_data)
print("数据已序列化并写入 person.bin")

运行这个脚本:

python create_data.py

现在你的目录下应该有了 person_pb2.pyperson.bin 两个文件。

示例:读取并解析数据

我们创建另一个脚本 read_data.py 来读取并解析 person.bin 文件。

# read_data.py
import person_pb2
# 1. 创建一个空的 Person 对象,用于填充数据
person = person_pb2.Person()
# 2. 从文件中读取二进制数据
with open("person.bin", "rb") as f:
    # 3. 解析二进制数据到 person 对象中
    person.ParseFromString(f.read())
# 4. 现在可以像访问普通 Python 对象一样访问数据
print("解析成功!")
print(f"Name: {person.name}")
print(f"ID: {person.id}")
print(f"Email: {person.email}")
print("\nPhones:")
for i, phone in enumerate(person.phones):
    # 枚举类型需要通过 .Name() 或 .value 来获取可读的值
    phone_type_str = person_pb2.Person.PhoneType.Name(phone.type)
    print(f"  Phone {i+1}: {phone.number} (Type: {phone_type_str})")

运行这个读取脚本:

python read_data.py

预期输出

解析成功!
Name: John Doe
ID: 123
Email: john.doe@example.com
Phones:
  Phone 1: 123-456-7890 (Type: HOME)
  Phone 2: 987-654-3210 (Type: WORK)

高级用法和常见场景

从字符串解析

如果你二进制数据已经在一个 Python 字符串变量中,可以直接使用 ParseFromString

binary_string = b'\x0a\x07\x4a\x6f\x68\x6e\x20\x44\x6f\x65\x10\x7b\x1a\x11john.doe@example.com\x22\x0c\x0a\x0c\x31\x32\x33\x2d\x34\x35\x36\x2d\x37\x38\x39\x30\x10\x01\x22\x0c\x0a\x0c\x39\x38\x37\x2d\x36\x35\x34\x2d\x33\x32\x31\x30\x10\x02'
person = person_pb2.Person()
person.ParseFromString(binary_string)
# ... 后续处理 person ...

JSON 格式

Protobuf 默认使用二进制格式,因为它高效且体积小,但如果你需要与人或其他系统交互,JSON 是更好的选择。

前提:你需要安装 protobuf 包的额外依赖。

pip install protobuf json

序列化为 JSON

import person_pb2
import json
# ... (假设 person 对象已经创建并填充了数据) ...
# 序列化为 JSON 字符串
json_string = json.dumps(person.__dict__) # 简单方法,但不完美
# 或者使用更通用的方法(推荐)
json_string = json.loads(person.SerializeToString()) # 这种方法不直接,需要先二进制再转
# 正确的 JSON 序列化方式需要额外库,如 `protoc-gen-json` 或手动实现。
# 一个更简单的方式是使用 `MessageToJson` (需要较新版本的 protobuf)
# from google.protobuf.json_format import MessageToJson
# json_string = MessageToJson(person)
print("JSON 格式:")
print(json_string)

从 JSON 解析

# from google.protobuf.json_format import Parse
# person_from_json = Parse(json_string, person_pb2.Person())
# 对于简单的 __dict__ 方法,可以这样反序列化(不推荐用于复杂结构)
# new_person = person_pb2.Person()
# new_person.ParseFromString(json.dumps(json.loads(json_string)))
# 这种方法很绕,不推荐,最好使用专门的 JSON 库或官方的 json_format。

读取包含多个消息的文件

一个常见的场景是,一个文件中包含了多个连续序列化的 Protobuf 消息,你需要循环读取直到文件末尾。

# 假设 multiple_persons.bin 文件包含了多个 Person 对象的二进制数据
def read_multiple_persons_from_file(filename):
    persons = []
    with open(filename, "rb") as f:
        while True:
            # 每个消息的大小是可变的,我们需要知道下一个消息有多长
            # 一个简单的方法是先读取 4 个字节作为长度(假设使用 int32)
            raw_length = f.read(4)
            if not raw_length:
                break # 文件结束
            # 将 4 字节的长度转换为整数
            length = int.from_bytes(raw_length, byteorder='little', signed=False)
            # 根据长度读取消息体
            data = f.read(length)
            if not data:
                break # 文件意外结束
            # 解析消息
            person = person_pb2.Person()
            person.ParseFromString(data)
            persons.append(person)
    return persons
# 使用示例
# persons_list = read_multiple_persons_from_file("multiple_persons.bin")
# for p in persons_list:
#     print(p.name)

步骤 操作 关键代码/命令
定义 创建 .proto 文件,定义消息结构 syntax = "proto3"; message Person { ... }
生成 使用 protoc 编译器生成 Python 代码 protoc -I=. --python_out=. person.proto
读取 导入生成的 *_pb2.py 模块
创建空消息实例
从文件/字符串读取二进制数据
调用 ParseFromString()
访问消息字段
import person_pb2
p = person_pb2.Person()
with open(...)
p.ParseFromString(data)
print(p.name)

希望这个详细的指南能帮助你掌握在 Python 中读取 Protobuf 数据!

分享:
扫描分享到社交APP
上一篇
下一篇