Looping over Protocol Buffers attributes in Python

Toby Wilkins picture Toby Wilkins · Mar 19, 2015 · Viewed 12.3k times · Source

I would like help with recursively looping over all attributes/sub objects contained in a protocol buffers message, assuming that we do not know the names of them, or how many there are.

As an example, take the following .proto file from the tutorial on the google website:

  message Person {
    required string name = 1;
    required int32 id = 2;
    optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

and to use it...:

person = tutorial.Person()
person.id = 1234
person.name = "John Doe"
person.email = "[email protected]"
phone = person.phone.add()
phone.number = "555-4321"
phone.type = tutorial.Person.HOME

Given Person, How do I then access both the name of the attribute and its value for each element: person.id, person.name, person.email, person.phone.number, person.phone.type?

I have tried the following, however it doesn't seem to recurs into person.phone.number or person.phone.type.

object_of_interest = Person

while( hasattr(object_of_interest, "_fields") ):
  for obj in object_of_interest._fields:
    # Do_something_with_object(obj) # eg print obj.name
    object_of_interest = obj

I have tried using obj.DESCRIPTOR.fields_by_name.keys to access the sub elements, but these are the string representations of the sub objects, not the objects themselves.

obj.name gives me the attribute of the name, but im not sure how to actually get the value of that attribute, eg obj.name may give me 'name', but how do i get 'john doe' out of it?

Answer

Jacob Burbach picture Jacob Burbach · Mar 19, 2015

I'm not super familiar with protobufs, so there may well be an easier way or api for this kind of thing. However, below shows an example of how you could iterate/introspect and objects fields and print them out. Hopefully enough to get you going in the right direction at least...

import addressbook_pb2 as addressbook

person = addressbook.Person(id=1234, name="John Doe", email="[email protected]")
person.phone.add(number="1234567890")


def dump_object(obj):
    for descriptor in obj.DESCRIPTOR.fields:
        value = getattr(obj, descriptor.name)
        if descriptor.type == descriptor.TYPE_MESSAGE:
            if descriptor.label == descriptor.LABEL_REPEATED:
                map(dump_object, value)
            else:
                dump_object(value)
        elif descriptor.type == descriptor.TYPE_ENUM:
            enum_name = descriptor.enum_type.values[value].name
            print "%s: %s" % (descriptor.full_name, enum_name)
        else:
            print "%s: %s" % (descriptor.full_name, value)

dump_object(person)

which outputs

tutorial.Person.name: John Doe
tutorial.Person.id: 1234
tutorial.Person.email: [email protected]
tutorial.Person.PhoneNumber.number: 1234567890
tutorial.Person.PhoneNumber.type: HOME