我有一个纯Python模块,我想用Cython重写一些子模块。然后我想将新的Cython子模块添加到原始的Python模块中,并使它们仅作为一个选项使用,这意味着cythoning模块不是强制性的(在这种情况下,旧的纯Python模块应该是使用)。
以下是一个例子:
my_module
- __init__.py
- a.py
- b.py
- setup.py
其中a.py
包含import b
。
我想在Cython中编写b.py
。想法是添加一个包含.pyx
文件的文件夹,例如:
my_module
- __init_.py
- a.py
- b.py
- setup.py
cython
-b.pyx
setup.py
将包含编译b.pyx
和安装模块的方向。但是,如果有人运行python setup.py install
,那么我希望安装纯Python代码,而如果添加了一个选项,则编译并安装Cython代码。
知道怎么做吗?
另外,如何修改文件a.py
以导入正确的模块?
答案 0 :(得分:2)
我不确定你的setup.py
要求(我不知道为什么你需要那个)但是对于运行时导入问题,我写了一个装饰器来做到这一点:
from __future__ import print_function
from importlib import import_module
from functools import wraps
import inspect
import sys
MAKE_NOISE = False
def external(f):
""" Decorator that looks for an external version of
the decorated function -- if one is found and
imported, it replaces the decorated function
in-place (and thus transparently, to would-be
users of the code). """
f.__external__ = 0 # Mark func as non-native
function_name = hasattr(f, 'func_name') and f.func_name or f.__name__
module_name = inspect.getmodule(f).__name__
# Always return the straight decoratee func,
# whenever something goes awry.
if not function_name or not module_name:
MAKE_NOISE and print("Bad function or module name (respectively, %s and %s)" % (
function_name, module_name), file=sys.stderr)
return f
# This function is `pylire.process.external()`.
# It is used to decorate functions in `pylire.process.*`,
# each of which possibly has a native (Cython) accelerated
# version waiting to be imported in `pylire.process.ext.*`
# … for example: if in `pylire/process/my_module.py` you did this:
#
# @external
# def my_function(*args, **kwargs):
# """ The slow, pure-Python implementation """
# pass
#
# … and you had a Cython version of `my_function()` set up
# in `pylire/process/ext/my_module.pyx` – you would get the fast
# function version, automatically at runtime, without changing code.
#
# TL,DR: you'll want to change the `pylire.process.ext` string (below)
# to match whatever your packages' structure looks like.
module_file_name = module_name.split('.')[-1]
module_name = "pylire.process.ext.%s" % module_file_name
# Import the 'ext' version of process
try:
module = import_module(module_name)
except ImportError:
MAKE_NOISE and print("Error importing module (%s)" % (
module_name,), file=sys.stderr)
return f
MAKE_NOISE and print("Using ext module: %s" % (
module_name,), file=sys.stderr)
# Get the external function with a name that
# matches that of the decoratee.
try:
ext_function = getattr(module, function_name)
except AttributeError:
# no matching function in the ext module
MAKE_NOISE and print("Ext function not found with name (%s)" % (
function_name,), file=sys.stderr)
return f
except TypeError:
# function_name was probably shit
MAKE_NOISE and print("Bad name given for ext_function lookup (%s)" % (
function_name,), file=sys.stderr)
return f
# Try to set telltale/convenience attributes
# on the new external function -- this doesn't
# always work, for more heavily encythoned
# and cdef'd function examples.
try:
setattr(ext_function, '__external__', 1)
setattr(ext_function, 'orig', f)
except AttributeError:
MAKE_NOISE and print("Bailing, failed setting ext_function attributes (%s)" % (
function_name,), file=sys.stderr)
return ext_function
return wraps(f)(ext_function)
...这允许你将函数装饰为@external
- 它们会在运行时自动替换为你提供的Cython优化版本。
如果你想将这个想法扩展到替换整个Cythonized类,那么在元类的__new__
方法中使用相同的逻辑会很简单(例如,优化模块中的机会主义查找和替换)
答案 1 :(得分:1)
我的解决方案是设置这样的模块:
my_module
- __init_.py
- a.py
- b.py
- setup.py
cython_my_module
- __init_.py
- b.pyx
setup.py
将包含与此类似的内容:
from distutils.core import setup
from Cython.Build import cythonize
import numpy
setup(
name='My_module',
ext_modules=cythonize(["cython_my_module/b.pyx",]),
include_dirs=[numpy.get_include()],
)
文件a.py
将在标题中包含以下行:
try:
import cython_my_module.b
except ImportError:
import b
它的工作方式非常简单:如果你不做任何事情(即如果你不编译cython文件)那么模块a.py
导入模块b.py
;但是,如果你运行python setup.py build_ext --inplace
,那么编译的cython文件将出现在cython_my_module
内,下次你运行a.py
时它会自动导入cython模块b.pyx
(实际上它会导入已编译的库b.so
)。
到目前为止它似乎工作,几乎不需要任何努力。希望它有所帮助。
fish2000解决方案似乎更通用,但我还没有尝试过。