我有一个 C 库,我正在尝试为其创建(外线,API 模式)CFFI 绑定。 C 库提供了每个函数的各种实现,但它们都添加了这个巨大的、令人讨厌的前缀。
例如,GreenSpam子模块中的foo函数的AVX优化实现被命名为
THELIBRARY_GREENSPAM_OPTIMIZED_AVX_foo()
;我会将其公开为 TheLibrary.Spam.GreenSpam.foo
,并在 import
时间选择透明的 CPU 优化。
我am能够在
ffibuilder.compile()
时间为每个模块确定这个前缀,所以我想尝试尽可能地倾向于DRY。我的代码目前看起来像这样,正如您所看到的,就 DRY 而言,这几乎是可怕的 并且 没有提供任何简单或明显的途径来在运行时算法选择上前进:
# TheLibrary/Spam/GreenSpam.py
from . import _greenspam_generic
from . import _greenspam_optimized_avx # TODO
__all__ = ['foo', 'oof']
def foo(bar):
"""GreenSpam foo"""
_out = _greenspam_generic.ffi.new(f"uint8_t[{len(bar):d}]")
_greenspam_generic.lib.THELIBRARY_GREENSPAM_GENERIC_foo(bar, len(bar), _out)
# void function never errors
return bytes(_out)
def oof(baz):
"""GreenSpam oof"""
_out = _greenspam_generic.ffi.new(f"uint8_t[{len(baz):d}]")
err = _greenspam_generic.lib.THELIBRARY_GREENSPAM_GENERIC_oof(baz, len(baz), _out)
if err:
raise RuntimeError("oof")
return bytes(_out)
# TheLibrary/Spam/PurpleSpam.py
from . import _purplespam_generic
from . import _purplespam_optimized_avx # TODO
__all__ = ['foo', 'oof']
def foo(bar):
"""PurpleSpam foo"""
_out = _purplespam_generic.ffi.new(f"uint8_t[{len(bar):d}]")
_purplespam_generic.lib.THELIBRARY_PURPLESPAM_GENERIC_foo(bar, len(bar), _out)
# void function never errors
return bytes(_out)
def oof(baz):
"""PurpleSpam oof"""
_out = _purplespam_generic.ffi.new(f"uint8_t[{len(baz):d}]")
err = _purplespam_generic.lib.THELIBRARY_PURPLESPAM_GENERIC_oof(baz, len(baz), _out)
if err:
raise RuntimeError("oof")
return bytes(_out)
我真的很想在运行
ffi.compile()
时展平这些 C 库函数名称,理想情况下公开诸如 (fr'\b{re.escape(prefix)}_(\w+)\b', r'\1')
之类的别名,以使我的 bindings' 代码更清晰、更好,特别是使我能够添加在这些导入时 CPU 选择中,无需再次重复我的代码。
我想尽可能密切地跟踪上游库,因此对 C 源代码进行转换以删除这些前缀会很痛苦,我想特别询问替代方案这样做。 但是,我可以很好地添加,例如在
ffibuilder.set_source()
期间,将规范化的
aliases添加到损坏的名称中,特别是如果可以以专注于DRY的方式完成的话。 (我确实尝试过函数引用,但显然那些只是C++?) 我目前正在查看的特定库是
,但我预计我也会在未来的绑定中遇到这个问题,所以这是反对过度深入转换 C 源代码的另一点,因为我会每次我遇到一个新库时都必须重新重复这项工作,并且可能每次该库重构其标头意大利面时都必须重复该工作。
# to make the builds "mylib_<suffix>.so"
for suffix in ['generic', 'optimized_avx']:
builder = FFIBuilder()
builder.set_source("mylib_" + suffix,
f"""
#include <mylib.h>
int foo_{suffix}(int x, int y);
void bar_{suffix}(void);
""")
builder.compile()
# minimal source code for mylib_generic.py
import _mylib_maker
globals().update(_mylib_maker.make("generic"))
# minimal source code for mylib_optimized_avx.py
import _mylib_maker
globals().update(_mylib_maker.make("optimized_avx"))
# actual Python-side wrappers defined in _mylib_maker.py
def make(suffix):
mod = __import__("mylib_" + suffix)
c_foo = getattr(mod, "foo_" + suffix)
c_bar = getattr(mod, "bar_" + suffix)
my_funcs = []
@my_funcs.append
def foo(x, y):
result = c_foo(x, y)
if result < 0: raise Exception
return result
@my_funcs.append
def bar():
return c_bar()
return {func.__name__: func for func in my_funcs}