Vig*_*oTW 6 python protocol-buffers python-3.x protobuf-python
protobuf 是否支持 python 的相对导入?
我未能成功创建支持此功能的 protobuf 构建脚本。从我的 .proto 文件生成 python 类时,如果我从与创建生成的 .py 文件的文件夹相同的文件夹中启动 python,我只能导入 python 模块。
我已经构建了以下 MVP。理想情况下,我想要一个结构,其中生成的 python 代码放置在一个单独的文件夹(例如./generated)中,然后我可以移动到其他项目中。我已经发布了我已经开始工作的方法,但我希望更有经验的人能够为我指出更好的解决方案。
基本信息:
文件夹结构:
.
|--- schemas
|---- main_class.proto
|---- sub
|----sub_class.proto
|--- generated
Run Code Online (Sandbox Code Playgroud)
syntax = "proto3";
import public "sub/sub_class.proto";
message MainClass {
repeated SubClass subclass = 1;
}
Run Code Online (Sandbox Code Playgroud)
syntax = "proto3";
message LogMessage {
enum Status {
STATUS_ERROR = 0;
STATUS_OK = 1;
}
Status status = 1;
string timestamp = 2;
}
message SubClass {
string name = 1;
repeated LogMessage log = 2;
}
Run Code Online (Sandbox Code Playgroud)
从根文件夹:
protoc -I=schemas --python_out=generated main_class.proto sub/sub_class.proto
这会将 python 文件放在./generated文件夹中。
使用上述方法,我可以在文件夹中启动 python./generated并使用
import main_class_pb2 as MC_proto.
但是,当我从.根文件夹(或任何其他文件夹)启动 python 时,使用
import generated.main_class_pb2 as MC_proto
产生错误ModuleNotFoundError: No module named 'sub'。基于这篇文章,我手动修改了生成的main_class_pb2.py-file 如下
# Original
# from sub import sub_class_pb2 as sub_dot_sub__class__pb2
# from sub.sub_class_pb2 import *
# Fix
from .sub import sub_class_pb2 as sub_dot_sub__class__pb2
from .sub.sub_class_pb2 import *
Run Code Online (Sandbox Code Playgroud)
通过.在 import 语句的开头添加in,我现在可以使用import generated.main_class_pb2 as MC_proto. 但是,每次都手动编辑生成的文件是非常不切实际的,所以我不喜欢这种方法。
我的第二种方法是尝试绝对导入。如果我知道我的项目根文件夹在哪里,我就可以将 .proto 文件移动到我想要 python 类所在的位置并在那里生成它们。在这个例子中,我使用了与之前相同的文件夹结构,但没有 - 文件夹./generated。我还必须更改 protoc 命令的根文件夹,这需要我修改main_class.proto文件中的导入语句,如下所示:
syntax = "proto3";
// Old
//import public "sub/sub_class.proto";
// New
import public "schemas/sub/sub_class.proto";
message MainClass {
repeated SubClass subclass = 1;
}
Run Code Online (Sandbox Code Playgroud)
protoc -I=. --python_out=. schemas/main_class.proto schemas/sub/sub_class.proto
假设我的根文件夹也是我项目的根文件夹,这种方法现在让我在根文件夹中启动 python 并使用导入模块
import schemas.main_class_pb2
但是,这意味着我的 .proto 文件必须与该项目中的 python 文件位于同一文件夹中,这看起来很混乱。这也意味着您必须从与项目相同的根文件夹生成 python 文件,这并不总是可行的。.proto 文件可能用于为两个完全不同的应用程序创建一个通用接口,并且必须维护两个略有不同的 protobuf 项目似乎违背了使用 protobuf 的目的。
我提供了一些示例 python 代码,可用于测试导入是否有效以及类是否按预期工作。此示例来自尝试 1,并假设从./generated文件夹启动 python
import main_class_pb2 as MC_proto
sub1, sub2 = (MC_proto.SubClass(name='sub1'),
MC_proto.SubClass(name='sub2'))
sub1.log.append(MC_proto.LogMessage(status=1, timestamp='2020-01-01'))
sub1.log.append(MC_proto.LogMessage(status=0, timestamp='2020-01-01'))
sub2.log.append(MC_proto.LogMessage(status=1, timestamp='2020-01-02'))
main = MC_proto.MainClass(subclass=[sub1, sub2])
main
Out[]:
subclass {
name: "sub1"
log {
status: STATUS_OK
timestamp: "2020-01-01"
}
log {
timestamp: "2020-01-01"
}
}
subclass {
name: "sub2"
log {
status: STATUS_OK
timestamp: "2020-01-02"
}
}
Run Code Online (Sandbox Code Playgroud)
生成 Python 代码时无法指示protoc使用相对导入。检查protocC++ 的源代码,很明显它只适用于绝对导入。看看下面:
src/google/protobuf/compiler/python/generator.cc -> 生成其他 proto 文件的导入部分的代码段
// Prints Python imports for all modules imported by |file|.
void Generator::PrintImports() const {
for (int i = 0; i < file_->dependency_count(); ++i) {
const std::string& filename = file_->dependency(i)->name();
std::string module_name = ModuleName(filename);
std::string module_alias = ModuleAlias(filename);
if (ContainsPythonKeyword(module_name)) {
// If the module path contains a Python keyword, we have to quote the
// module name and import it using importlib. Otherwise the usual kind of
// import statement would result in a syntax error from the presence of
// the keyword.
printer_->Print("import importlib\n");
printer_->Print("$alias$ = importlib.import_module('$name$')\n", "alias",
module_alias, "name", module_name);
} else {
int last_dot_pos = module_name.rfind('.');
std::string import_statement;
if (last_dot_pos == std::string::npos) {
// NOTE(petya): this is not tested as it would require a protocol buffer
// outside of any package, and I don't think that is easily achievable.
import_statement = "import " + module_name;
} else {
import_statement = "from " + module_name.substr(0, last_dot_pos) +
" import " + module_name.substr(last_dot_pos + 1);
}
printer_->Print("$statement$ as $alias$\n", "statement", import_statement,
"alias", module_alias);
}
CopyPublicDependenciesAliases(module_alias, file_->dependency(i));
}
printer_->Print("\n");
// Print public imports.
for (int i = 0; i < file_->public_dependency_count(); ++i) {
std::string module_name = ModuleName(file_->public_dependency(i)->name());
printer_->Print("from $module$ import *\n", "module", module_name);
}
printer_->Print("\n");
}
Run Code Online (Sandbox Code Playgroud)
此函数使用module_name在您的第一次尝试中生成以下代码片段:
from sub import sub_class_pb2 as sub_dot_sub__class__pb2
from sub.sub_class_pb2 import *
Run Code Online (Sandbox Code Playgroud)
来自以下module_name函数:ModuleName
// Returns the Python module name expected for a given .proto filename.
std::string ModuleName(const std::string& filename) {
std::string basename = StripProto(filename);
ReplaceCharacters(&basename, "-", '_');
ReplaceCharacters(&basename, "/", '.');
return basename + "_pb2";
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,该函数中没有用于生成相对导入的标志或逻辑。
IMO,我认为最好的方法是使用你的第二次尝试,但在不同的包上,然后你可以从 Python 代码中导入它。
| 归档时间: |
|
| 查看次数: |
1731 次 |
| 最近记录: |