Tensorflow的Protocol Buffers3编程(三)

作者:langb2014

接下来是Java的API文档。

编译器调用

当使用--java_out= 命令行标记时,protocol buffer编译器生成java输出。--java_out= 选项的参数是想编译器写java输出的目录。编译器为每个.proto文件输入创建一个单一的.java文件.这个文件包含一个单一的outer class定义,包含一些内嵌类和静态字段,基于.proto文件中的定义。

outer class的名字如下可选:如果.proto文件包含如下的一行:

option java_outer_classname = "Foo";

则outer class的名字将会是 Foo。否则,outer class名字通过转换.proto文件的基本名称为驼峰法来决定。例如,foo_bar.proto将变成FooBar。如果在文件中有相同名字的消息,"OuterClass"将被附加到outer class的名字后。例如,如果 foo_bar.proto 包含一个名为 FooBar 的消息,outer class将变成 FooBarOuterClass 。

Java package名字的选择在后面的 Packages 一节中描述。

通过连接参数到 --java_out= 来选择输出文件, package 名称(使用/替代.)和 .java 文件名。

因此,例如,如下调用编译器:

protoc --proto_path=src --java_out=build/gen src/foo.proto

如果 foo.proto 的java package是 com.example 并且它的outer class名称是 FooProtos,那么protocol buffer编译器将生成文件 build/gen/com/example/FooProtos.java。protocol buffer编译器在需要时将自动创建 build/gen/com 和 build/gen/com/example 目录。但是,它将不会创建 build/gen 或者 build 。他们必须已经存在。可以在一次调用中指定多个 .proto文件;所有输出文件将被一次性生成。

==当输出java代码时,protocol buffer编译器直接输出到jar包的能力是非常方便的,因为很多java工具可以直接从jar文件中读取源代码。要输出到jar文件,简单的提供以.jar结束的输出位置。注意,仅有java源代码被放置在包中;还是必须单独编译来生成java class文件==

Python生成的代码

Packages/包

生成的类被放置在一个基于 java_package 选项的java package中。如果这个选项缺失,将替代使用 package 定义。

例如,如果 .proto 文件包含:

package foo.bar;

那么结果的java类将放置在Java package foo.bar中。不过,如果 .proto 文件也包含一个 java_package 选项,像这样:

package foo.bar;
option java_package = "com.example.foo.bar";

那么类将放置在 com.example.foo.bar package中。提供 java_package 选项是因为通常 .proto package 定义不会以倒置的域名来开始。

Python生成的代码

消息

给出一个简单的消息定义:

message Foo {}

protocol buffer 编译器生成名为 Foo 的类,实现 Message 接口。这个类被定义为 final, 不容许任何子类。Foo 继承自 GeneratedMessage, 但是这个可以认为是实现细节。默认, Foo 用为实现最大速度的特别版本来覆盖很多 GeneratedMessage 的方法。不过,如果 .proto 文件包含这行:

option optimize_for = CODE_SIZE;

则 Foo 将只覆盖功能必要方法的最小集合,而其他方法依赖 GeneratedMessage 的基于反射的实现。这显著的降低了生成代码的大小,但是也降低了性能。再不然,如果 .proto 文件包含:

option optimize_for = LITE_RUNTIME;

那么 Foo 将包含所有方法的快速实现,但是将实现 MessageLite 接口,MessageLite 接口只包含 Message 的方法子集。尤其,它不支持描述符和反射。但是,在这种模式下,生成的代码仅需要链接到libprotobuf-lite.jar而不是libprotobuf.jar。这个"lite"类库比完全类库小很多,而且更加适合资源受限系统例如移动手机。

Message 接口定义方法让你检查,操纵,读取或者写入完整的消息。在这些方法之外, Foo class定义了下列静态方法:

  • static Foo getDefaultInstance(): 返回Foo的单例实例,这等效于调用Foo.newBuilder().build() (这样所有字段被重置而所有重复字段清空)。注意消息的默认实例可以通过调用它的newBuilderForType()用来作为工厂(factory)。
  • static Descriptor getDescriptor(): 返回类型的描述符。这包含关于类型的信息,包括它有什么字段和他们的类型是什么。这可以和消息的反射方法一起使用,例如 getField().
  • static Foo parseFrom(...): 从给定的资源解析类型 Foo 的消息并返回它。在Message.Builder接口中的mergeFrom()的每个变量有一个对应的parseFrom方法。注意parseFrom()从来不抛出UninitializedMessageException; 如果被解析的消息缺失必要的字段它会抛出InvalidProtocolBufferException。这使得它和调用Foo.newBuilder().mergeFrom(...).build()有微妙的不同。
  • static Parser parser(): 返回解析器实例,它实现了多个parseFrom()方法
  • Foo.Builder newBuilder(): 创建一个新的builder(下面描述).
  • Foo.Builder newBuilder(Foo prototype): 创建一个新的builder,所有字段初始化为和在prototype中一样的值。因为内嵌消息和字符串对象是不可变,他们将会在原始对象和复制对象之间共享。

Builders

Message 对象 - 例如上面描述的 Foo class的实例 - 是不可变的,就像Java字符串。为了构建一个消息对象,需要使用builder。每个message类有它自己的bilder类 - 因此在我们的Foo例子中,protocol buffer编译器生成了一个内嵌类 Foo.Builder 可以用来构建Foo。Foo.Builder实现Message.Builder接口。它继承自GeneratedMessage.Builder类,但是,再次,这应该视为实现细节。和Foo类似,Foo.Builder 可能依靠范型方法实现,或者,当optimize_for选项被使用时,生成的定制化代码要快的多。

Foo.Builder没有定义任何静态方法。它的接口和通过Message.Builder定义的完全一样,不同的是返回类型更加明确:Foo.Builder的方法修改了builder返回类型Foo.Builder,而build()返回类型Foo。

builder的修改内容的方法 - 包括字段setter - 通常返回builder的引用(例如,他们"return this;")。这容许多个方法调用可以在一行中链起来。例如:

builder.mergeFrom(obj).setFoo(1).setBar("abc").clearBaz();

Sub Builders

对于包含子消息的消息,编译器也生成子builder。这容许你反复修改深层嵌套的子消息而不用重新构建他们。例如:

message Foo {
  optional int32 val = 1;
  // some other fields.
}

message Bar {
  optional Foo foo = 1;
  // some other fields.
}

message Baz {
  optional Bar bar = 1;
  // some other fields.
}

如果你已经有了一个Baz消息,并且想修改深度内嵌的在Foo中的val。以其:

If you have a Baz message already, and want to change the deeply nested val in Foo. Instead of:

baz = baz.toBuilder().setBar(
    baz.getBar().toBuilder().setFoo(
      baz.getBar().getFoo().toBuilder().setVal(10).build()
    ).build()).build();

你可以写:

Baz.Builder builder = baz.toBuilder();
    builder.getBarBuilder().getFooBuilder().setVal(10);
    baz = builder.build();

内嵌类型

消息可以在其他消息内部定义。例如: message Foo { message Bar { } }

在这种情况下,编译器简单的生成Bar作为内部类内嵌在Foo中。

Python生成的代码

字段

除了在前一节描述的方法之外,protocol buffer编译器为在.proto文件中定义的消息的每个字段生成访问器方法集合。读取字段的方法被定义在消息类和它对应的builder上,修改值的方法仅在builder上被定义。

注意方法名称通常使用驼峰法命名,就是在.proto文件中的字段名使用带下划线的小写(应该如此)。转换工作如下:

  1. 对于名字中的每个下划线,下划线被删除,而下一个字母大写。
  2. 如果名字有附加前缀(如 "get"),第一个字母大写,否则小写

因此,字段 foo_bar_baz 变成 fooBarBaz. 如果带 get 前缀, 将是 getFooBarBaz.

除访问方法之外,编译器为每个字段生成一个包含它的字段编号的整型常量。常量名是转换为大写并加上 _FIELD_NUMBER 字段名字。例如,假设字段 optional int32 foo_bar = 5。编译器将生成常量 public static final int FOO_BAR_FIELD_NUMBER = 5;

Singular Fields (proto2)

对于这些字段定义的任何一个:

optional int32 foo = 1;
required int32 foo = 1;

编译器将在消息类和它的builder中生成下列访问器方法:

  • boolean hasFoo(): 如果字段被设置返回true
  • int getFoo(): 返回字段的当前值。如果字段没有设置,返回默认值。

编译器将仅在消息的builder中生成下列方法:

  • Builder setFoo(int value): 设置字段的值。调用之后,hasFoo()将返回true而getFoo()将返回这个值。
  • Builder clearFoo(): 清除字段的值。调用之后,hasFoo() 将返回 false 而 getFoo() 将返回默认值。

对于其他简单字段类型,将根据 scalar value types table 选择合适的java类型。对于消息和枚举类型,值类型被消息或者枚举类代替。

内嵌消息字段

对于消息类型,setFoo() 方法也接受消息builder类型的实例作为参数。这仅仅是一个快捷方式,等价于调用 builder的.build()方法并将结果传递给方法。

如果字段没有设置, getFoo() 方法将返回一个任何字段都没有设置的 Foo 实例(很可能是Foo.getDefaultInstance()返回的实例)。

此外,编译器生成两个访问器方法容许你访问消息类型关联的subbuilder。下列方法将被消息类和它的builder中生成:

  • FooOrBuilder getFooOrBuilder(): 如果字段的builder已经存在则返回字段的builder;如果不存在则返回消息

编译器仅在消息的builder上生成下列方法:

  • Builder getFooBuilder(): 返回字段的builder

Singular Fields (proto3)

对于这个字段定义:

int32 foo = 1;

编译器将在消息类和它的builder中生成下列访问器方法:

  • int getFoo(): 返回字段的当前值。如果字段没有设置,返回默认值。

编译器将仅在消息的builder中生成下列方法:

  • Builder setFoo(int value): 设置字段的值。调用之后,hasFoo()将返回true而getFoo()将返回这个值。
  • Builder clearFoo(): 清除字段的值。调用之后,hasFoo() 将返回 false 而 getFoo() 将返回默认值。

对于其他简单字段类型,将根据 scalar value types table 选择合适的java类型。对于消息和枚举类型,值类型被消息或者枚举类代替。

内嵌消息字段

对于消息字段类型,将在消息类和它的builder上生成一个额外的访问器方法:

  • boolean hasFoo(): 如果字段已经设置则返回true
  • setFoo() 也接受消息builder类型的实例作为参数。这仅仅是一个捷方式,等价于调用 builder 的.build()方法并将结果传递给方法。

如果字段没有设置, getFoo() 方法将返回一个任何字段都没有设置的 Foo 实例(很可能是Foo.getDefaultInstance()返回的实例)。

此外,编译器生成两个访问器方法容许你访问消息类型关联的subbuilder。下列方法将被消息类和它的builder中生成:

  • FooOrBuilder getFooOrBuilder(): 如果字段的builder已经存在则返回字段的builder;如果不存在则返回消息

编译器仅在消息的builder上生成下列方法:

  • Builder getFooBuilder(): 返回字段的builder

枚举字段

对于枚举字段类型,将会在消息类和它的builder上生成额外的访问器方法:

  • int getFooValue(): 返回枚举的整型值

编译器仅在消息的builder上生成下列方法:

  • Builder setFooValue(int value): 设置枚举的整型值。

此外, 如果枚举类型未知,getFoo() 将返回 UNRECOGNIZED - 这是一个特别的附加值, 由proto3编译器添加到生成的枚举类型。

重复字段

对于这个字段定义:

repeated int32 foo = 1;
```language

编译器将在消息类和它的builder中生成下列访问器方法:

  • int getFooCount(): 返回当前字段中的元素数量
  • int getFoo(int index): 返回给定的以0为基准的下标位置的元素。
  • List<Integer> getFooList(): 以列表方式返回所有字段。如果字段没有设置,返回一个空列表。返回的列表是对于消息类不可变而对于消息builder类不可修改(原文:The returned list is immutable for message classes and unmodifiable for message builder classes.)。

编译器仅在消息的builder上生成下列方法:

  • Builder setFoo(int index, int value): 设置给定以0为基准的下标位置的元素的值
  • Builder addFoo(int value): 使用给定的值附加一个新元素到字段中(最后面)
  • Builder addAllFoo(Iterable<? extends Integer> value): 将给定 Iterable 中的所有元素都附加到字段中(最后面)
  • Builder clearFoo(): 从字段中删除所有字段。调用这个方法之后,getFooCount()将返回0.

对于其他简单字段类型,将根据 scalar value types table 选择合适的java类型。对于消息和枚举类型,值类型被消息或者枚举类代替。

重复内嵌消息字段

对于消息类型,setFoo() 和 addFoo() 也接受消息builder类型的一个实例作为参数。这仅仅是一个快捷方式,等价于调用 builder的.build()方法并将结果传递给方法。

此外,编译器将为消息类型在消息类和它的builer上生成两个额外的访问器方法,容许你访问关联的subbuilder:

  • FooOrBuilder getFooOrBuilder(int index): 如果特定元素的builder已经存在则返回builder;如果不存在则返回元素。如果这个方法调用来自消息类,它将总是返回消息而不是builder。
  • List<FooOrBuilder> getFooOrBuilderList(): 以不可修改的builder列表(如果可用)的方式返回整个字段。如果这个方法调用来自消息类,它将总是返回消息的不可变列表而不是不可修改的builder列表。

编译器仅在消息的builder上生成下列方法:

  • Builder getFooBuilder(int index): 返回指定下标的元素的builder。
  • Builder addFooBuilder(int index): 为位于指定下标的默认消息实例附加并返回builder。
  • Builder addFooBuilder(): 为默认消息实例附加并返回builder。
  • Builder removeFoo(int index): 删除位于给定以0为基准的下标位置的元素。
  • List<Builder> getFooBuilderList(): 以不可修改的builder列表的方式返回整个字段

重复枚举字段 (仅用于proto3)

编译器将在消息类和它的builder上生成下列额外方法:

  • int getFooValue(int index): 返回位于指定下标的枚举的整型值
  • List getFooValueList(): 以Integer列表的方式返回整个字段。

编译器将仅在消息的builder上生成下列额外方法:

  • Builder setFooValue(int index, int value): 设置位于指定下标的枚举的整型值。

Oneof 字段

对于这个oneof字段定义:

oneof oneof_name {
    int32 foo = 1;
    ...
}

编译器将在消息类和它的builder中生成下列访问器方法:

  • boolean hasFoo() (仅用于proto2): 如果oneof实例是Foo则返回true。
  • int getFoo(): 如果oneof实例是Foo则返回oneof_name的当前值。否则,返回这个字段的默认值。

编译器将仅在消息的builder中生成下列方法:

  • Builder setFoo(int value): 设置 oneof_name 为这个值并设置oneof实例为FOO。调用这个方法之后, hasFoo()将返回true,getFoo()将返回值而getOneofNameCase()将返回FOO。
  • Builder clearFoo():

    • 如果oneof实例不是FOO则不会有任何变化
    • 如果oneof实例是FOO,设置 oneof_name 为null,oneof实例为 ONEOF_NAME_NOT_SET。调用这个方法之后,hasFoo()将返回false,getFoo()将返回默认值而getOneofNameCase()将返回 ONEOF_NAME_NOT_SET 。

对于其他简单字段类型,将根据 scalar value types table 选择合适的java类型。对于消息和枚举类型,值类型被消息或者枚举类代替。

Map 字段

对于这个 map 字段定义:

    map<int32, int32> weight = 1;

编译器将在消息类和它的builder中生成下列访问器方法:

  • Map<Integer, Integer> getWeight();: 返回不可修改的map。

编译器将仅在消息的builder中生成下列方法:

  • Map<Integer, Integer> getMutableWeight();: 返回可变的Map。注意对这个方法的多次调用将返回不同的map实例。返回的map引用可能对任何后续对builder的方法调用无效。
  • Builder putAllWeight(Map<Integer, Integer> value);: 添加在给定map中的所有记录到这个字段。

Any

假设有一个类似这样的Any字段:

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  google.protobuf.Any details = 2;
}

在我们生成的代码中,details字段的getter返回一个com.google.protobuf.Any的实例。这提供下面特殊方法来打包和拆包Any的值:

class Any {
  // 打包给定的消息到一个Any中,使用默认类型URL前缀 “type.googleapis.com”.
  public static Any pack(Message message);
  // 打包给定消息到一个Any中,使用给定类型URl前缀
  public static Any pack(Message message,
                         String typeUrlPrefix);

  // 检查这个Any消息的负载(payload)是不是给定的类型
  public <T extends Message> boolean is(class<T> clazz);

  // 解包Any到给定的消息类型。如果类型不匹配或者解析负载失败则抛出异常
  public <T extends Message> T unpack(class<T> clazz)
      throws InvalidProtocolBufferException;
}

Oneof

给假设有一个类似这样的oneof定义:

oneof oneof_name {
    int32 foo_int = 4;
    string foo_string = 9;
    ...
}

在 oneof_name 这个 oneof 中的所有字段将为他们的值使用共享的 oneof_name 对象。此外,protocol buffer compiler 将为每个oneof case生成一个Java枚举类型,如下所示:

public enum OneofNameCase implements com.google.protobuf.Internal.EnumLite {
    FOO_INT(4),
    FOO_STRING(9),
    ...
    ONEOF_NAME_NOT_SET(0);
    ...
};

这个枚举类型的值有下列特殊方法:

  • int getNumber(): 返回对象在.proto文件中定义的编号
  • static Foo valueOf(int value): 返回对应给定编号的枚举对象,对于其他编号则返回null。

编译器也将在消息类和它的builder中生成下列访问方法:

  • OneofNameCase getOneofNameCase(): 返回枚举指示哪个字段被设置。如果一个都没有设置返回 ONEOF_NAME_NOT_SET

编译器将仅在消息的builder中生成下列方法:

  • Builder clearOneofName(): 设置 oneof_name 为null,并设置 oneof case 为ONEOF_NAME_NOT_SET

枚举

假设有这样的枚举定义:

enum Foo {
  VALUE_A = 0;
  VALUE_B = 5;
  VALUE_C = 1234;
}

protocol buffer 编译器将生成一个名为 Foo 的Java枚举类型,带有同样的值集合。如果正在使用proto3,还将添加特殊值 UNRECOGNIZED 到枚举类型。生成的枚举类型的值有下列特殊方法:

  • int getNumber():返回对象定义在.proto文件中的编号
  • EnumValueDescriptor getValueDescriptor(): 返回枚举值的描述符,包含枚举值的信息如值的名字,编号和类型。
  • EnumDescriptor getDescriptorForType(): 返回枚举类型的描述符,包含关于每个定义值的信息。

此外,Foo 枚举类型包含下列静态方法:

  • static Foo forNumber(int value): 返回对应给定编号值的枚举对象。如果没有对应的枚举对象则返回null。
  • static Foo valueOf(int value): 返回对应给定编号的枚举对象。这个方法被弃用,以forNumber(int value)方法替代,并将在即将出现的发布版本中移除。
  • static Foo valueOf(EnumValueDescriptor descriptor): 返回对应给定枚举值描述符的枚举对象。可能比valueOf(int)快。在proto3中如果传递一个未知的枚举值描述符则会返回 UNRECOGNIZED
  • EnumDescriptor getDescriptor(): 返回枚举类型的描述符,包含关于每个定义的枚举值的信息(和getDescriptorForType() 唯一的差别在于它是一个静态方法)

为每个枚举值生成一个带有 _VALUE 后缀的整型常量。

注意 .proto 语言容许多个枚举符号拥有同一个编号。有相同编号的符号是同义词。例如:

enum Foo {
    BAR = 0;
    BAZ = 0;
}

在这个案例中, BAZ 是 BAR 的同义词。在Java中, BAZ 将被定义为一个像这样的static fianl 字段:

static final Foo BAZ = BAR;

因此, BAR 和 BAZ 是相同的, 而 BAZ 应该从不出现在 switch 语句中。编译器通常选择用给定编号定义的第一个符号作为这个符号的"权威"版本。所有随后有同样编号的符号仅仅是别名。

枚举可以在消息类型中内嵌定义。编译器生成内嵌在消息类型类中的java枚举定义。

扩展 (仅用于proto2)

假设有一个消息带有扩展范围:

message Foo {
    extensions 100 to 199;
}

protocol buffer编译器将让 Foo 继承 GeneratedMessage.ExtendableMessage 而不是通常的 GeneratedMessage. 类型的, Foo的builder将继承 GeneratedMessage.ExtendableBuilder 。你应该从不通过名字来引用这些基本类型(GeneratedMessage 被认为是实现细节)。然而,这些超类定义了一些额外的方法让你可以用来操作扩展。

尤其 Foo 和 Foo.Builder 将继承方法 hasExtension(), getExtension() 和 getExtensionCount()。此外, Foo.Builder 将继承方法 setExtension() 和 clearExtension()。每个这些方法都将获取一个扩展标识符(下面描述),作为它们的第一个参数,用来标记一个扩展字段。剩下的参数和返回值和为同样类型的普通(非扩展)字段而生成的那些对应的访问器方法完全相同。

给出一个扩展定义:

extend Foo {
    optional int32 bar = 123;
}

protocol buffer 编译器生成一个名为 bar 的"扩展标识符",可以和 Foo的扩展访问器一起使用来访问这个扩展,像这样:

Foo foo =
    Foo.newBuilder()
     .setExtension(bar, 1)
     .build();
assert foo.hasExtension(bar);
assert foo.getExtension(bar) == 1;

(扩展标识符的确切实现非常复杂而且涉及到范型的不可意思的(magical)的使用 - 不过,在使用时你不用担心扩展标识符是如何工作。)

注意 bar 将被声明为.proto文件的outer class的静态字段, 如 前面描述 的。在这个例子中,我们忽略了outer class 的名字。

扩展可以内嵌在其他类型中申明。例如,通用模式是这样:

message Baz {
    extend Foo {
        optional Baz foo_ext = 124;
    }
}

在这个案例中,扩展标识符 foo_ext 内嵌在 Baz 中声明。它可以像下面这样使用:

Baz baz = createMyBaz();
Foo foo =
    Foo.newBuilder()
    .setExtension(Baz.fooExt, baz)
    .build();

当解析一个可能有扩展的消息时,必须提供一个 ExtensionRegistry ,在这里已经注册了需要能够解析的任何扩展。否则,那些扩展将被当成未知字段。例如:

ExtensionRegistry registry = ExtensionRegistry.newInstance();
registry.add(Baz.fooExt);
Foo foo = Foo.parseFrom(input, registry);
发表评论

0个评论

我要留言×

技术领域:

我要留言×

留言成功,我们将在审核后加至投票列表中!

提示x

人工智能开发框架知识库已成功保存至我的图谱现在你可以用它来管理自己的知识内容了

删除图谱提示×

你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?

删除节点提示×

无法删除该知识节点,因该节点下仍保存有相关知识内容!

删除节点提示×

你确定要删除该知识节点吗?