Help us understand the problem. What is going on with this article?

gRPC-Pythonでprotocolbuffers型を扱うときに使うメソッドCopyFrom, Extendについて

pythonでgRPCを使用する場合、protocolbuffer(pb2)型を使用することになります。
このprotocolbuffer型は扱う際に注意する点がいくつかあったので記事にまとめます。

CopyFromメソッドを使用したprotocolbuffer型の代入処理

例えば、以下のようなproto定義があるとします。メソッド1つのシンプル設計です。

user.proto
service UserService {
  rpc GetUser(GetUserRequest) returns (User) {}
}

message Size {
  int32 height = 1;
  int32 weight = 2;
}

message User {
  string id = 1;
  string name = 2;
  Size size = 3;
}

message GetUserRequest {
  string user_id = 1;
}

以下は上記のprotoファイルを使用したUserServiceのサーバーサイド実装例です。

import user_pb2

class UserService():
  def GetUser(self, request, context):
    # dbのユーザをget
    db_user = db.get(request.user_id)

    # sizeを定義し、値を追加
    size = user_pb2.Size()
    size.height = db_user.height
    size.weight = db_user.weight

    # userを定義し、値を追加して返却
    user = user_pb2.User()
    user.id = db_user.id
    user.name = db_user.name
    user.size = size

    return user

上記のGetUserメソッドを実行すると、TypeErrorでエラーとなってしまいます。
どこが問題かというと、コード最後から2番目の行user.size = sizeの部分です。

Size型のオブジェクト型であるsizeはUser型で定義されているsizeの型と同じですが、そのまま代入するとエラーとなってしまいます。
ここで使用するのがCopyFromというメソッドです。
上記のコードの問題箇所を以下のように書き換えることでTypeErrorなくコードが実行されます。

import user_pb2

class UserService():
  def GetUser(self, request, context):
    # dbのユーザをget
    db_user = db.get(request.user_id)

    # sizeを定義し、値を追加
    size = user_pb2.Size()
    size.height = db_user.height
    size.weight = db_user.weight

    # userを定義し、値を追加して返却
    user = user_pb2.User()
    user.id = db_user.id
    user.name = db_user.name
    user.size.CopyFrom(size)

    return user

Typeメソッドを使って比較してもsizeオブジェクトはSize型なので気付きにくいですが、protocolbuffer型として代入する際はCopyFromを使用しなければ正しく代入できません。

Extendを使用したrepeatedな型の代入

repeatedな定義でも代入時に気をつける点があります。
前のUserServiceを書き換えた次のprotoファイルを使用して説明します。

User.proto
service UserService {
  rpc GetUser(GetUserRequest) returns (User) {}
}

message Item {
  string id = 1;
  string name = 2;
}

message User {
  string id = 1;
  string name = 2;
  repeated Item items = 3;
}

message GetUserRequest {
  string user_id = 1;
}

sizeをitemsというrepeatedな型に変えました。以下がUserServiceのコードです。

import user_pb2

class UserService():
  def GetUser(self, request, context):
    # dbのユーザをget
    db_user = db.get(request.user_id)

    # itemを複数定義し、itemsリストを作成
    item1 = user_pb2.Item()
    item1.id = '1'
    item1.name = 'item1'

    item2 = user_pb2.Item()
    item2.id = '2'
    item2.name = 'item2'

    items = [item1, item2]

    # userを定義し、値を追加して返却
    user = user_pb2.User()
    user.id = db_user.id
    user.name = db_user.name
    user.items = items

    return user

上記のコードを実行すると、やはりTypeErrorが発生します。問題箇所は同じくuser.items = itemsの部分です。
repeatedなフィールドに代入する場合はextendメソッドを使用します。

    user.items.extend(items)

上記のようにextendを使用することで配列の型に値を設定することが可能になります。

なぜこのように扱う必要があるのかはgoogleの公式ページ等で確認しましたが、いまいち説明されていませんでした。
protocolbufferはPython上だと癖がある感じなので注意して使っていきましょう!

Tanashun
Python/gRPC/AWS/iOSに興味があります。ラズパイ周りもちょっと詳しくなりました。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away