Search code examples
pythonprotocol-bufferssetattr

How to avoid `setattr` (and `getattr`) when using Python? And is it necessary to avoid


If I want to add a value to a field in a protocol buffer that isn't known at compile time, I'm currently doing setattr. I normally don't like using setattr because it seems less secure. But when I know the object is a protobuf, I'm thinking it is fine, because the value I'm setting it to must be of the type that the protobuf allows. So maybe it isn't really unsafe??

Let me explain by example. First, assume I have this protobuf:

message Person {
  string first_name = 1;
  string second_name = 1;
  int age = 3;
}

Then I have some code that uses the above protobuf:

from person_pb2 import Person

my_info = {"first_name": "Mike", "last_name": "example", "age": 999}
me = Person()
for field, value in my_info:
  setattr(me, field, value)

This is a very flexible way to handle the protobuf. I cannot, for instance, specify it like I would in a dict, saying me[field] = value. Yet, me[field] = value is perfectly safe. If the value is of the wrong type for the field/attribute when using setattr, I'll get an error: TypeError: bad argument type for built-in operation

So, I'm tempted to say that for protobufs, using setattr is completely fine and, in fact, is really the only way to programmatically add values to the protobuf fields. Is this correct? Is there a better or safer way? I cannot do something like me.first_name = "Mike" because I need it to be programmatic.


Solution

  • While calling setattr on a normal object in general can be insecure because one can potentially override internal or dunder attributes with nafarious values, it is completely fine to use setattr to programmatically set values on protobuf message fields because protobuf overrides __setattr__ of the message type with safeguard logics to make sure that only valid fields with valid values can be set.

    Excerpt from the protobuf documentation:

    Note that these assignments are not just adding arbitrary new fields to a generic Python object. If you were to try to assign a field that isn’t defined in the .proto file, an AttributeError would be raised. If you assign a field to a value of the wrong type, a TypeError will be raised. Also, reading the value of a field before it has been set returns the default value.

    person.no_such_field = 1  # raises AttributeError
    person.id = "1234"        # raises TypeError