背景

最近学习抽空学习gRPC,准备为后面项目的需求提前做好预研,主要是Java与Python的交互,所以就打算使用gRPC来实现这个功能,在学习过程中遇到了这个问题,在此记录一下,避免过段时间就忘了。

项目结构

1
2
3
4
5
6
7
pygrpc
client
xxxclient.py
example
helloworld.proto
server
xxxserver.py

生成命令

1
python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. ./helloworld.proto
1
2
3
4
5
6
7
8
9
10
/home/charles/miniconda3/bin/conda run -n py_grpc --no-capture-output python /mnt/d/Program Files/JetBrains/PyCharm 2022.2.1/plugins/python/helpers/pydev/pydevd.py --multiprocess --qt-support=auto --client 127.0.0.1 --port 41177 --file /mnt/d/PycharmProjects/pygrpc/client/greeter_client.py 
Connected to pydev debugger (build 223.8836.43)
Traceback (most recent call last):
File "<frozen importlib._bootstrap>", line 1042, in _handle_fromlist
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/mnt/d/PycharmProjects/pygrpc/example/helloworld_pb2_grpc.py", line 5, in <module>
import helloworld_pb2 as helloworld__pb2
ModuleNotFoundError: No module named 'helloworld_pb2'
ERROR conda.cli.main_run:execute(33): Subprocess for 'conda run ['python', '/mnt/d/Program Files/JetBrains/PyCharm 2022.2.1/plugins/python/helpers/pydev/pydevd.py', '--multiprocess', '--qt-support=auto', '--client', '127.0.0.1', '--port', '41177', '--file', '/mnt/d/PycharmProjects/pygrpc/client/greeter_client.py']' command failed. (See above for error)

报错分析

由于*_pb2_grpc.py会使用*_pb2.py中定义的各种类型,因此两者之间存在import关系,而问题恰恰出在这个import上。

注意我使用的指令:

*python -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. helloworld.proto

中间的空格都被我给区分开了,上述指令的意思我们解读一下:

helloworld.proto将所在目录为当前目录的``helloworld.proto`进行处理

--proto_path=. 将当前目录添加到搜索路径,用这个参数来寻找你proto文件中的import的文件路径

--python_out=.生成的_pb2.py存放于当前目录

--grpc_python_out=.生成的_pb2_grpc.py存放于当前目录

原因

在GitHub上找到了issue,问题在helloworld.proto路径,同时也是执行目录的问题(./helloworld.proto)。

Python下protoc的解析方案是根据proto文件的相对路径来确定导入路径的,也就是说,假如我的目录如下:

1
2
3
4
5
6
7
pygrpc
client
xxxclient.py
example
helloworld.proto
server
xxxserver.py

我如果想正确的编译,那么我应该在pygrpc下使用

1
python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. example/helloworld.proto

注意到指令末尾改为了相对路径example/helloworld.proto,而--python_out--grpc_python_out的目录相对于当前目录,执行完该指令后,protoc将会在--python_out生成proto/helloworld_pb2.py,在--grpc_python_out下生成example/helloworld_pb2_grpc.py,一般我们会将--python_out--grpc_python_out指定为同一个目录。

再次展示我们的项目文件路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
│  main.py

├─client
│ greeter_client.py

├─example
│ │ helloworld.proto
│ │ helloworld_pb2.py
│ │ helloworld_pb2_grpc.py
│ │ __init__.py
│ │
│ └─__pycache__
│ helloworld_pb2.cpython-38.pyc
│ helloworld_pb2_grpc.cpython-38.pyc
│ __init__.cpython-38.pyc

└─server
greeter_server.py