3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

gRPCAdvent Calendar 2019

Day 10

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

Last updated at Posted at 2019-12-09

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上だと癖がある感じなので注意して使っていきましょう!

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?