1. 项目概述为什么需要C调用Python在工程实践中我们常常会遇到一个两难的局面一方面C以其卓越的运行时性能和对系统底层资源的精细控制能力成为高性能计算、游戏引擎、嵌入式系统等领域的基石另一方面Python凭借其简洁的语法、丰富的生态库如NumPy、Pandas、TensorFlow、PyTorch和极高的开发效率在数据分析、机器学习、快速原型构建方面无可替代。将两者结合取长补短就催生了C与Python混合编程的需求。具体到“C调用Python”这个场景其核心价值在于我们可以在一个以C为主体架构的性能敏感型应用中灵活地嵌入Python的逻辑用以实现那些变化频繁、算法复杂或依赖特定Python库的功能模块。想象一下你正在开发一个实时的图形处理引擎C实现但其中某个图像特效的算法团队的数据科学家用Python和OpenCV已经迭代出了一个非常优秀的版本。重写为C不仅耗时而且后续算法更新又会带来同样的问题。这时如果引擎能直接调用这个Python脚本问题就迎刃而解了。再比如一个用C编写的高频交易系统需要集成一个用Python的TA-Lib库实现的、仍在不断优化的量化策略模型。C调用Python本质上是在寻求一种“鱼与熊掌兼得”的工程方案用C守住性能与稳定的底线用Python拥抱灵活与生态的上限。这不仅仅是技术上的缝合更是一种务实的架构设计思想。2. 核心原理与方案选型CPython API vs. pybind11要实现C调用Python业界主要有两大技术路径它们各有优劣适用于不同的场景。2.1 原生的CPython C API这是最基础、最直接的方式。Python解释器本身CPython就是用C写的它提供了一套完整的C API允许C/C代码与Python解释器交互可以执行代码、调用函数、操作对象。工作原理简述初始化解释器C代码首先调用Py_Initialize()启动一个Python解释器实例。导入模块与获取函数使用PyImport_ImportModule导入.py文件或已安装的模块然后用PyObject_GetAttrString获取模块中的函数对象。构建参数与调用将C变量如intdoublestd::string通过Py_BuildValue等函数打包成Python的元组PyTuple作为参数然后使用PyObject_CallObject调用获取到的函数对象。解析结果与清理函数返回一个Python对象PyObject*需要将其解包成C类型如用PyArg_ParseTuple最后务必管理好引用计数Py_DECREF防止内存泄漏。终结解释器程序退出前调用Py_Finalize()。优点零依赖无需任何第三方库是Python官方的标准方式。极致控制对Python解释器的生命周期、对象内存管理有完全的控制权。适合学习理解其过程能深刻把握Python与C交互的底层机制。缺点代码冗长且易错大量的样板代码boilerplate code手动管理PyObject引用计数非常繁琐极易导致内存泄漏或崩溃。类型转换麻烦C复杂类型如自定义类、STL容器与Python类型的相互转换需要大量手动编码。维护成本高当接口发生变化时同步更新C包装代码的工作量很大。注意对于新手而言直接使用纯C API就像用汇编语言写业务逻辑虽然强大但效率低下容易出错不建议在复杂的生产项目中作为首选。2.2 现代的pybind11库pybind11是一个轻量级的、只包含头文件的C库它本质上是一个强大的“胶水”生成器。它的设计哲学是让C代码暴露给Python变得像写C本身一样自然。它通过大量的模板元编程技巧自动处理了类型转换、引用计数、异常传递等脏活累活。工作原理简述 你只需用一套简洁的宏和语法描述你的C函数、类告诉pybind11如何映射。在编译时pybind11会生成所有必要的C API胶水代码编译成一个动态链接库.pydon Windows.soon Linux。这个库可以直接被Python的import语句导入就像导入一个纯Python模块一样。优点语法优雅使用C11及以上语法定义接口的代码非常直观、简洁。自动类型转换支持std::vectorstd::mapEigen::Matrix等复杂类型与Pythonlistdictnumpy.ndarray的自动转换这是其杀手级特性。自动内存管理通过智能指针std::unique_ptrstd::shared_ptr与Python引用计数的集成基本无需手动管理内存。功能丰富支持继承、重载、异常传递、文档字符串生成等高级特性。社区活跃已成为C/Python互操作的事实标准广泛用于科研和工业界。缺点引入依赖需要将pybind11作为项目的一部分头文件即可。编译稍复杂需要确保编译环境能找到正确的Python头文件和库。对C版本有要求主要基于C11/14特性。方案选择建议 对于入门学习和简单调用理解C API的原理是必要的但实操可以从pybind11开始。对于绝大多数实际项目尤其是需要暴露复杂C类或使用复杂数据类型的场景pybind11是不二之选。它极大地提升了开发效率和代码可维护性。本文将主要围绕pybind11展开因为它代表了当前的最佳实践。3. 环境准备与工具链配置工欲善其事必先利其器。一个正确且高效的环境是成功的第一步。3.1 Python环境与开发包安装首先你需要一个Python解释器。从 python.org 下载并安装。关键点在于必须安装“Python开发包”。在Windows上安装时请务必勾选“pip”和“Add Python to PATH”。更重要的是对于使用Visual Studio的用户安装器可能会提供一个“Download debug binaries”选项这有助于后续调试。在Linux上你需要安装python3-dev或python3-devel包例如Ubuntu下sudo apt-get install python3-dev。安装后在命令行验证python --version pip --version同时确认Python的包含文件include和库文件libs路径。在Windows上它们通常在C:\Users\用户名\AppData\Local\Programs\Python\PythonXX\include和...\libs。在Linux上通常在/usr/include/python3.x和/usr/lib。3.2 C编译器的选择WindowsVisual Studio 2019/2022是首选。它集成了MSVC编译器、调试器和优秀的C支持。社区版免费。确保安装时勾选“使用C的桌面开发”工作负载。Linux/macOSGCC或Clang。通常系统自带或可通过包管理器轻松安装如sudo apt-get install g。3.3 构建系统CMake是标配现代C项目尤其是涉及混合编程和跨平台的强烈推荐使用CMake作为构建系统。它能自动查找Python库、配置编译参数管理依赖像pybind11。安装CMake从 cmake.org 下载安装。获取pybind11推荐作为项目的子模块submodule。在你的项目根目录git submodule add https://github.com/pybind/pybind11.git extern/pybind11备用直接下载发布包解压到项目目录中。3.4 集成开发环境IDE配置Visual Studio对Windows用户最友好。创建一个新的“CMake项目”VS会自动识别CMakeLists.txt。VS Code跨平台轻量灵活。需要安装扩展C/C(Microsoft)CMake Tools(Microsoft)Python(Microsoft) 配置settings.json和CMakeLists.txt后VS Code可以提供智能感知、编译和调试支持。实操心得在Windows上一个常见的坑是“Debug”和“Release”模式下Python库的链接问题。Debug模式需要链接Python的调试库如python3xx_d.lib而Release模式链接python3xx.lib。使用CMake的find_package(Python ...)命令可以很好地处理这个问题。在VS Code或VS中务必注意顶部工具栏中的“配置”Configuration选项确保与你当前Python环境匹配。4. 实战使用pybind11创建你的第一个C扩展模块让我们从一个最简单的例子开始创建一个C函数它接收两个整数并返回它们的和然后将这个函数暴露给Python。4.1 项目目录结构my_project/ ├── CMakeLists.txt # 项目根CMake文件 ├── extern/ │ └── pybind11/ # pybind11子模块或源码 └── src/ ├── CMakeLists.txt # 模块的CMake文件 └── my_module.cpp # 我们的C扩展源码4.2 编写C扩展代码 (src/my_module.cpp)// my_module.cpp #include pybind11/pybind11.h // 核心头文件 namespace py pybind11; // 创建一个别名方便使用 // 一个简单的C函数 int add(int i, int j) { return i j; } // 使用PYBIND11_MODULE宏创建Python模块。 // 第一个参数“my_module”是模块名在Python中import的名字 // 第二个参数“m”是一个py::module_对象代表这个模块。 PYBIND11_MODULE(my_module, m) { m.doc() pybind11 example plugin; // 可选的模块文档字符串 // 使用def()函数将C函数“add”暴露给Python。 // 参数依次为Python中的函数名、C函数指针、文档字符串。 m.def(add, add, A function which adds two numbers); // 你还可以暴露变量 m.attr(the_answer) 42; py::object world py::cast(World); m.attr(what) world; }这段代码清晰展示了pybind11的简洁性。PYBIND11_MODULE是入口m.def()用于暴露函数m.attr()用于暴露属性。4.3 编写CMake构建脚本根目录CMakeLists.txt:cmake_minimum_required(VERSION 3.10...3.26) project(MyPybindProject) # 设置C标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 将pybind11作为子目录添加这样我们就可以使用它的函数了 add_subdirectory(extern/pybind11) # 添加我们的源码子目录 add_subdirectory(src)src/CMakeLists.txt:# 使用pybind11提供的工具函数来添加一个Python模块 pybind11_add_module(my_module # 生成的模块名称必须与C代码中PYBIND11_MODULE的第一个参数一致 my_module.cpp # 模块的源文件 ) # 可选设置更友好的输出名称在Windows上会生成my_module.pyd而不是my_module.cp39-win_amd64.pyd set_target_properties(my_module PROPERTIES OUTPUT_NAME my_module) # 可选设置后缀通常不需要pybind11_add_module已处理 # set_target_properties(my_module PROPERTIES SUFFIX .pyd)4.4 编译与构建生成构建系统在项目根目录打开终端或VS Code的终端创建一个构建目录并进入。mkdir build cd build运行CMake生成对应平台的工程文件。Windows (VS):cmake .. -G Visual Studio 17 2022 -A x64Linux/macOS:cmake ..编译Windows (VS): 打开生成的MyPybindProject.sln选择Release或Debug配置生成解决方案。或者使用命令行cmake --build . --config Release。Linux/macOS: 直接运行make。编译成功后你会在build目录下的某个子文件夹如Release 或lib里找到生成的模块文件my_module.pydWindows或my_module.soLinux/macOS。4.5 在Python中调用将生成的.pyd或.so文件所在的目录添加到Python的模块搜索路径或者直接在该目录下启动Python解释器。import sys sys.path.insert(0, rpath\to\your\build\output\directory) # 添加路径 import my_module # 导入我们刚编译的C扩展 print(my_module.add(1, 2)) # 输出3 print(my_module.the_answer) # 输出42 print(my_module.what) # 输出World如果一切顺利你将看到输出结果。恭喜你已经完成了C到Python的第一次调用避坑技巧模块名冲突确保PYBIND11_MODULE中的模块名、pybind11_add_module中的目标名、以及Python中import的名字三者完全一致且不与已安装的Python包重名。Python环境匹配编译使用的Python解释器版本、位数-32/64必须和运行时调用的Python解释器完全一致。否则会导致导入失败ImportError。使用python -c import sys; print(sys.version); print(sys.executable)确认运行时环境。动态库依赖在Linux下有时需要设置LD_LIBRARY_PATH指向Python库的路径或者使用patchelf工具修改生成.so文件的rpath。CMake的find_package(Python ...)通常能处理好链接。5. 进阶处理复杂数据类型与C类简单的函数调用只是开始。混合编程的真正威力在于传递复杂数据。5.1 STL容器与Python内建类型的自动转换pybind11为许多标准库类型提供了开箱即用的转换支持。#include pybind11/pybind11.h #include pybind11/stl.h // 关键必须包含此头文件以支持STL转换 #include vector #include map #include string namespace py pybind11; // 返回一个向量 std::vectorint get_vector() { return {1, 2, 3, 4, 5}; } // 接收一个映射字典并修改它 void process_map(std::mapstd::string, int m) { m[processed] 1; } PYBIND11_MODULE(complex_types, m) { m.def(get_vector, get_vector); m.def(process_map, process_map); }在Python中import complex_types vec complex_types.get_vector() # 返回一个list: [1,2,3,4,5] print(type(vec)) # class list my_dict {a: 10, b: 20} complex_types.process_map(my_dict) print(my_dict) # {a: 10, b: 20, processed: 1}注意#include pybind11/stl.h它启用了std::vectorstd::mapstd::string等与Pythonlistdictstr的自动转换。对于std::pair和std::tuple也有相应的头文件pybind11/functional.h等。5.2 暴露C类到Python这是pybind11最强大的功能之一能让Python以近乎原生类的方式操作C对象。#include pybind11/pybind11.h #include string namespace py pybind11; class Pet { public: // 构造函数 Pet(const std::string name) : name(name) { } // 成员函数 void setName(const std::string name_) { name name_; } const std::string getName() const { return name; } // 静态函数 static std::string getSpecies() { return Animal; } private: std::string name; }; PYBIND11_MODULE(example_class, m) { py::class_Pet(m, Pet) // 定义Python类“Pet”绑定到C类Pet .def(py::initconst std::string ()) // 暴露构造函数 .def(setName, Pet::setName) // 暴露成员函数 .def(getName, Pet::getName) .def_static(getSpecies, Pet::getSpecies) // 暴露静态函数 .def(__repr__, // 定义Python的repr()行为 [](const Pet a) { return example.Pet named a.getName() ; } ); }Python中使用import example_class p example_class.Pet(Molly) print(p.getName()) # Molly p.setName(Chloe) print(p) # example.Pet named Chloe print(example_class.Pet.getSpecies()) # Animalpy::class_模板是核心。通过链式调用.def()我们可以轻松地暴露构造函数、成员函数、静态函数甚至重载Python的特殊方法如__repr____str____call__。5.3 与NumPy数组的无缝交互使用Eigen或直接缓冲区科学计算中在C和Python间传递大型数值数组时避免拷贝是关键。pybind11通过pybind11::array_t和Eigen库支持提供了强大的支持。方法一使用pybind11::array_t轻量级#include pybind11/pybind11.h #include pybind11/numpy.h namespace py pybind11; // 一个函数接收一个NumPy数组只读并返回其所有元素的和 double sum_array(py::array_tdouble input) { // 请求一个缓冲区信息对象它包含了数据指针、形状、步长等信息 auto buf input.request(); double *ptr (double *) buf.ptr; // 获取原始数据指针 int size buf.size; // 获取元素总数 double sum 0; for (int i 0; i size; i) { sum ptr[i]; } return sum; } // 一个函数创建一个新的NumPy数组并返回 py::array_tdouble create_array(int size) { // 分配内存并初始化这里归pybind11管理 auto result py::array_tdouble(size); auto buf result.request(); double *ptr (double *) buf.ptr; for (int i 0; i size; i) { ptr[i] i * 1.1; } return result; } PYBIND11_MODULE(numpy_example, m) { m.def(sum_array, sum_array); m.def(create_array, create_array); }方法二与Eigen库集成推荐用于线性代数Eigen是C中强大的线性代数库。pybind11有官方支持。首先确保你的CMake能找到Eigen例如通过find_package(Eigen3 REQUIRED)或作为子模块。#include pybind11/pybind11.h #include pybind11/eigen.h // 关键头文件 #include Eigen/Dense namespace py pybind11; using Eigen::MatrixXd; using Eigen::VectorXd; // 矩阵乘法 MatrixXd mat_mul(const MatrixXd a, const MatrixXd b) { return a * b; } // 接收一个NumPy数组作为Eigen::Ref避免拷贝就地修改 void scale_vector(Eigen::RefVectorXd v, double factor) { v * factor; } PYBIND11_MODULE(eigen_example, m) { m.def(mat_mul, mat_mul); m.def(scale_vector, scale_vector); }在Python端你可以直接传递NumPy数组import numpy as np import eigen_example A np.random.rand(3, 3).astype(np.float64) B np.random.rand(3, 3).astype(np.float64) C eigen_example.mat_mul(A, B) # 返回一个新的NumPy数组 print(C) v np.array([1.0, 2.0, 3.0], dtypenp.float64) eigen_example.scale_vector(v, 2.0) # 原地修改v print(v) # [2. 4. 6.]Eigen::Ref是一个非常高效的包装器它允许C函数直接操作NumPy数组底层的内存而无需任何数据拷贝这对于处理大规模数据至关重要。实操心得当传递大型数组时务必注意数据在内存中的连续性C-contiguous或F-contiguous。NumPy数组默认是C连续行优先这与Eigen的默认存储顺序列优先可能不匹配。如果顺序不匹配Eigen的Ref或Map可能会触发隐式拷贝。使用Eigen::RefMatrixXd, Eigen::RowMajor可以指定行优先以匹配NumPy的默认行为避免意外拷贝。6. 内存管理、异常处理与线程安全6.1 智能指针与对象生命周期在C中暴露类时对象的所有权谁负责删除是个重要问题。pybind11与C11智能指针无缝集成。class MyObject { public: MyObject(int v) : value(v) {} int value; }; std::shared_ptrMyObject create_shared() { return std::make_sharedMyObject(42); } PYBIND11_MODULE(smart_ptr_demo, m) { py::class_MyObject, std::shared_ptrMyObject(m, MyObject) .def(py::initint()) .def_readwrite(value, MyObject::value); m.def(create_shared, create_shared); }通过将std::shared_ptrMyObject作为py::class_的第二个模板参数我们告诉pybind11使用共享指针来管理这个Python对象的底层C对象。当Python侧没有任何引用指向这个对象时C对象会被自动销毁。你也可以使用std::unique_ptr但需要注意所有权的转移语义。6.2 C异常传递到Python当C函数抛出异常时pybind11会自动将其转换为对应的Python异常。#include stdexcept void risky_function(int x) { if (x 0) { throw std::runtime_error(Negative input not allowed!); } // ... 正常逻辑 } PYBIND11_MODULE(exception_demo, m) { m.def(risky_function, risky_function); }在Python中调用risky_function(-1)你会收到一个RuntimeError消息是“Negative input not allowed!”。pybind11内置了大多数标准库异常std::runtime_errorstd::logic_error等到Python异常的映射。你也可以使用py::register_exception注册自定义异常。6.3 全局解释器锁GIL与线程安全Python有一个全局解释器锁GIL它阻止多个线程同时执行Python字节码。在C线程中调用任何Python API包括通过pybind11暴露的函数之前必须先获取GIL。pybind11提供了py::gil_scoped_acquire和py::gil_scoped_release来方便地管理GIL。#include thread #include iostream void call_python_from_cpp_thread() { // 这个函数将在C创建的线程中运行 py::gil_scoped_acquire acquire; // 获取GIL // 现在可以安全地调用Python函数或操作Python对象了 py::print(Hello from C thread!); // GIL在acquire对象析构时函数结束时自动释放 } void start_thread() { std::thread t(call_python_from_cpp_thread); t.detach(); // 或使用join() } PYBIND11_MODULE(thread_demo, m) { m.def(start_thread, start_thread); }重要规则从Python主线程发起的调用GIL已持有无需额外操作。在C线程中回调Python必须先获取GIL使用py::gil_scoped_acquire。长时间运行的C函数如果函数不涉及任何Python操作纯C计算可以在开始时释放GILpy::gil_scoped_release release;让其他Python线程得以运行计算完成后再重新获取如果需要返回Python对象。警告错误地管理GIL是导致程序死锁或随机崩溃的最常见原因之一。在多线程环境下使用pybind11必须格外小心。7. 调试技巧与常见问题排查混合编程的调试比纯C或纯Python要复杂一些因为错误可能发生在任何一层。7.1 编译期常见错误找不到Python.h确保CMake正确找到了Python开发包。检查find_package(Python REQUIRED COMPONENTS Development)是否成功以及Python_INCLUDE_DIRS变量是否被正确设置。链接错误LNK2019 LNK2001通常是找不到Python的导入库.lib文件。确保Python_LIBRARIES变量正确并且链接器路径包含Python的libs目录。在Windows上Debug和Release的库文件不同要区分开。pybind11头文件找不到检查add_subdirectory(pybind11)或find_package(pybind11)是否正确执行。7.2 运行时常见错误ImportError: DLL load failed(Windows) 或ImportError: cannot open shared object file(Linux)原因1生成的模块.pyd/.so与当前Python解释器不兼容版本、位数。解决方案用python -c import sys; print(sys.version); print(sys.executable)确认环境并用此环境下的Python和编译器重新编译。原因2模块依赖的其他DLL如特定的MSVC运行时库找不到。解决方案将Python安装目录包含python3xx.dll添加到系统PATH或使用Dependency WalkerWindows/lddLinux工具查看缺失的依赖。AttributeError: module xxx has no attribute yyy原因C中暴露的函数/类名与Python中调用的名字不一致或者模块初始化失败。解决方案检查PYBIND11_MODULE宏中的模块名、m.def中的函数名是否拼写正确。在C模块的初始化函数开头加一句py::print(Module initializing...);看是否执行。程序在调用Python函数后崩溃Segmentation Fault原因1GIL管理不当多线程场景。解决方案仔细检查线程中调用Python代码前后是否正确地获取/释放了GIL。原因2C对象生命周期问题。一个指向已销毁C对象的Python包装器被访问。解决方案使用智能指针shared_ptr管理对象生命周期。原因3在C中使用了无效的py::object或py::handle。解决方案使用obj.is_none()或obj.is_valid()检查对象有效性。性能问题原因在频繁调用的接口中进行了不必要的Python/C类型转换。解决方案使用py::call_guardpy::gil_scoped_release()包装不涉及Python操作的纯C函数减少GIL争夺。对于数值计算尽量使用Eigen::Ref或py::array_t直接操作内存避免拷贝。7.3 调试策略分而治之先确保你的C代码本身逻辑正确写一个纯C测试程序。再确保你的Python脚本本身能运行。最后再将两者结合。使用打印调试在C代码的关键位置使用py::print()它是线程安全的且会自动处理GIL或std::cout注意多线程可能输出混乱输出信息。使用调试器Visual Studio这是最强大的工具。将Python解释器python.exe设置为调试启动程序参数为你的测试脚本。在C扩展模块的代码中设置断点。当Python脚本import并调用你的模块时调试器就会在断点处停下。VS Code配置一个“混合模式”的launch.json。通常需要两个配置一个启动Python调试器并附加到C进程。这需要一些配置但网上有成熟的教程。GDB (Linux)可以先运行Python脚本然后用gdb -p pid附加到进程或者在C代码中插入raise(SIGTRAP)来触发中断。查看Python跟踪信息当Python端抛出异常时完整的traceback信息对于定位C中哪一行出了问题非常有帮助。确保你的异常能正确传递。8. 项目构建与分发的最佳实践当你的混合编程项目需要交付给他人或在多台机器上部署时构建和分发就变得重要。8.1 使用setuptools与pip进行打包虽然CMake是构建C扩展的主流但Python生态的标准打包工具是setuptools。我们可以结合两者或者直接使用setuptools的Extension模块。纯setuptools方式 (setup.py):from setuptools import setup, Extension import pybind11 # 定义扩展模块 ext_modules [ Extension( my_package.my_module, # 点号表示包内的模块 sources[src/my_module.cpp], include_dirs[pybind11.get_include(), src/], languagec, extra_compile_args[/std:c17 if sys.platform win32 else -stdc17], ), ] setup( namemy_package, ext_modulesext_modules, # ... 其他元数据 )然后运行pip install .进行构建和安装。这种方式的好处是与Python的打包生态完全融合。结合CMake与setuptools (pybind11官方推荐): 对于更复杂的、依赖其他C库的项目可以使用scikit-build基于CMake的setuptools替代品或CMakeLists.txtsetup.py的混合模式。pybind11的官方文档提供了详细的例子。8.2 跨平台编译考量编译器标志Linux/macOS下常用-fPIC位置无关代码、-O3优化Windows下注意/MD与/MT运行时库的选择通常使用/MD以与Python DLL匹配。二进制兼容性在Windows上由不同版本Visual Studio编译的二进制模块可能不兼容。通常要求编译器和Python解释器使用相同的主要MSVC版本。依赖管理如果你的C扩展依赖第三方库如Boost OpenCV你需要确保这些库在目标系统上可用或者将它们静态链接到你的模块中。8.3 持续集成CI配置在GitHub Actions GitLab CI或Travis CI中自动化构建和测试至关重要。一个典型的CI流程包括安装特定版本的Python和C编译器如actions/setup-pythonactions/setup-msbuild。安装项目依赖通过pip install -r requirements.txt。配置、编译扩展模块cmakemake或msbuild。运行Python单元测试pytest。这能确保你的代码在不同环境下都能正常工作。我个人在实际项目中的体会是从“能用”到“好用”关键在于清晰的接口设计和完善的错误处理。定义C暴露给Python的API时要尽量保持Pythonic——使用Python常见的命名风格、利用关键字参数、提供合理的默认值。同时C侧要对传入的参数做充分的检查和验证给出清晰的错误信息因为Python用户看到的错误信息就来自于此。最后文档字符串py::arg().doc()非常重要它能被Python的help()函数和Sphinx等文档工具读取极大提升模块的易用性。混合编程是一把瑞士军刀用好了能解决棘手问题但也需要对两种语言的特性和边界有更深的理解。