1. 背景
最近学习抽空学习gRPC,准备为后面项目的需求提前做好预研,主要是Java与Python的交互,所以就打算使用gRPC来实现这个功能,在学习过程中遇到了这个问题,在此记录一下,避免过段时间就忘了。
2. 项目结构
pygrpc
client
xxxclient.py
example
helloworld.proto
server
xxxserver.py
3. 生成命令
python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. ./helloworld.proto
输出如下日志:
/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)
4. 报错分析
由于*_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
存放于当前目录
5. 原因
在GitHub上找到了issue,问题在helloworld.proto
路径,同时也是执行目录的问题(./helloworld.proto
)。
Python下protoc
的解析方案是根据proto
文件的相对路径来确定导入路径的,也就是说,假如我的目录如下:
pygrpc
client
xxxclient.py
example
helloworld.proto
server
xxxserver.py
我如果想正确的编译,那么我应该在pygrpc
下使用
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
指定为同一个目录。
再次展示我们的项目文件路径:
│ 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