我参与的一个项目是使用名为UniFFI的 Rust 语言绑定库从 Rust 代码创建 Python 语言绑定。crate 生成一个本地库(比如libtinymath.dylib
)和一个 Python 模块(比如tinymath.py
导入和使用本地库。
python 包布局如下所示:
❯ tree
.
├── src
│ ├── tinymath
│ │ ├── __init__.py
│ │ ├── tinymath.py
│ │ └── libtinymath.dylib
├── tests
│ └── test_tinymath.py
├── setup.py
└── tox.ini
我的setup.py
构建文件确保在 buildtools 创建库时将库复制到包中:
# setup.py
from setuptools import setup, find_packages
setup(
name='tinymath',
version='0.0.1',
packages=find_packages(where="src"),
package_dir={"": "src"},
package_data={"tinymath": ["*.dylib"]},
include_package_data=True,
zip_safe=False,
)
问题是使用 pip 安装库后,导入语句失败,说明 Python 在默认路径中找不到本机库。这是有道理的,因为该库确实不在 Python 使用的路径中(我可以从返回的错误中看到)。我通过搜索这个问题的答案了解到,实际上 Python 搜索路径对于普通 Python 模块(使用PYTHONPATH
)与原生库不同。这就是给我带来麻烦的原因。如果我愿意,我可以更改 Python 查找模块的位置,但我似乎无法调整 Python 查找本机库的位置。
核心问题
因此,我着手更改包用于在 Linux 和 macOS 上搜索本机库的路径;我所需要的只是确保它看起来“在其自身内部”(本机库确实位于模块旁边)。我不确定这是否应该在setup.py
文件中或通过其他方法指定。这是我在这里要问的核心问题:当它寻找本机库时,如何告诉包在当前目录中查找?
替代方法
可以用来完成这项工作的另一种途径是准确声明从模块中找到库的位置(tinymath.py
在上面的示例中)。但我宁愿不使用这种方法,主要是因为这意味着进入并使用 UniFFI 提供的自动生成的语言绑定模块(他们建议不要触摸)。但是,它确实有效,验证了问题仅在于搜索路径并且其他一切正常的想法。
使用这种方法,我当前的修复是从其自动生成的版本更改库加载功能:
def loadIndirect():
if sys.platform == "linux":
libname = "lib{}.so"
elif sys.platform == "darwin":
libname = "lib{}.dylib"
elif sys.platform.startswith("win"):
# As of python3.8, ctypes does not seem to search $PATH when loading DLLs.
# We could use `os.add_dll_directory` to configure the search path, but
# it doesn't feel right to mess with application-wide settings. Let's
# assume that the `.dll` is next to the `.py` file and load by full path.
libname = os.path.join(
os.path.dirname(__file__),
"{}.dll",
)
return getattr(ctypes.cdll, libname.format("tinymath"))
到修改后的版本:
def loadIndirect():
if sys.platform == "linux":
# libname = "lib{}.so"
libname = os.path.join(os.path.dirname(__file__), "lib{}.so")
elif sys.platform == "darwin":
# libname = "lib{}.dylib"
libname = os.path.join(os.path.dirname(__file__), "lib{}.dylib")
elif sys.platform.startswith("win"):
# As of python3.8, ctypes does not seem to search $PATH when loading DLLs.
# We could use `os.add_dll_directory` to configure the search path, but
# it doesn't feel right to mess with application-wide settings. Let's
# assume that the `.dll` is next to the `.py` file and load by full path.
libname = os.path.join(
os.path.dirname(__file__),
"{}.dll",
)
return getattr(ctypes.cdll, libname.format("tinymath"))