gpt4 book ai didi

python - 只打包用 Cython 编译的 python 库的二进制编译 .so 文件

转载 作者:太空宇宙 更新时间:2023-11-03 23:59:31 43 4
gpt4 key购买 nike

我有一个名为 mypack 的包,里面有一个模块 mymod.py,并且__init__.py。出于某种不在争论中的原因,我需要打包这个模块编译(也不允许 .py 或 .pyc 文件)。也就是说,__init__.py 是唯一的分布式压缩文件中允许的源文件。

文件夹结构是:

. 

├── mypack
│ ├── __init__.py
│ └── mymod.py
├── setup.py

我发现 Cython 可以通过转换 .so 库中的每个 .py 文件来做到这一点可以直接用python导入。

问题是:setup.py 文件必须如何才能轻松打包和安装?

目标系统有一个 virtualenv,必须在其中安装软件包任何允许轻松安装和卸载的方法(easy_install、pip 等都是欢迎)。

我尝试了我力所能及的一切。我阅读了 setuptoolsdistutils 文档,所有与计算器相关的问题,并尝试了各种命令(sdist、bdist、bdist_egg 等),其中有很多setup.cfg 和 MANIFEST.in 文件条目的组合。

我得到的最接近的是下面的安装文件,它将子类化 bdist_egg命令也删除 .pyc 文件,但这会破坏安装。

在 venv 中“手动”安装文件的解决方案是也很好,前提是包含在适当文件中的所有辅助文件安装包括在内(我需要在 venv 中运行 pip freeze 并查看mymod==0.0.1).

运行它:

python setup.py bdist_egg --exclude-source-files

并(尝试)用

安装它
easy_install mymod-0.0.1-py2.7-linux-x86_64.egg

您可能会注意到,目标是使用 python 2.7 的 64 位 linux。

from Cython.Distutils import build_ext
from setuptools import setup, find_packages
from setuptools.extension import Extension
from setuptools.command import bdist_egg
from setuptools.command.bdist_egg import walk_egg, log
import os

class my_bdist_egg(bdist_egg.bdist_egg):

def zap_pyfiles(self):
log.info("Removing .py files from temporary directory")
for base, dirs, files in walk_egg(self.bdist_dir):
for name in files:
if not name.endswith('__init__.py'):
if name.endswith('.py') or name.endswith('.pyc'):
# original 'if' only has name.endswith('.py')
path = os.path.join(base, name)
log.info("Deleting %s",path)
os.unlink(path)

ext_modules=[
Extension("mypack.mymod", ["mypack/mymod.py"]),
]

setup(
name = 'mypack',
cmdclass = {'build_ext': build_ext,
'bdist_egg': my_bdist_egg },
ext_modules = ext_modules,
version='0.0.1',
description='This is mypack compiled lib',
author='Myself',
packages=['mypack'],
)

更新。按照@Teyras 的回答,可以按照答案中的要求构建一个轮子。 setup.py 文件内容为:

import os
import shutil
from setuptools.extension import Extension
from setuptools import setup
from Cython.Build import cythonize
from Cython.Distutils import build_ext

class MyBuildExt(build_ext):
def run(self):
build_ext.run(self)
build_dir = os.path.realpath(self.build_lib)
root_dir = os.path.dirname(os.path.realpath(__file__))
target_dir = build_dir if not self.inplace else root_dir
self.copy_file('mypack/__init__.py', root_dir, target_dir)

def copy_file(self, path, source_dir, destination_dir):
if os.path.exists(os.path.join(source_dir, path)):
shutil.copyfile(os.path.join(source_dir, path),
os.path.join(destination_dir, path))


setup(
name = 'mypack',
cmdclass = {'build_ext': MyBuildExt},
ext_modules = cythonize([Extension("mypack.*", ["mypack/*.py"])]),
version='0.0.1',
description='This is mypack compiled lib',
author='Myself',
packages=[],
include_package_data=True )

关键点是设置packages=[],。需要覆盖 build_extrun 方法才能在轮子中获取 __init__.py 文件。

最佳答案

不幸的是,the answer suggesting setting packages=[]是错误的,可能会破坏很多东西,例如可见于 this question .不要使用它。您不应从 dist 中排除所有包,而应仅排除将被 cython 化并编译为共享对象的 python 文件。

下面是一个工作示例;它使用 my recipe来自问题Exclude single source file from python bdist_egg or bdist_wheel .示例项目包含包含两个模块的包 spamspam.eggsspam.bacon,以及一个子包 spam.fizz 与一个模块 spam.fizz.buzz:

root
├── setup.py
└── spam
├── __init__.py
├── bacon.py
├── eggs.py
└── fizz
├── __init__.py
└── buzz.py

模块查找是在 build_py 命令中完成的,因此您需要使用自定义行为对其进行子类化。

简单案例:编译所有源代码,不做任何异常(exception)

如果您要编译每个 .py 文件(包括 __init__.py),覆盖 build_py.build_packages 就足够了> 方法,让它成为一个 noop。因为 build_packages 不做任何事情,所以根本不会收集任何 .py 文件,并且 dist 将只包含 cythonized 扩展:

import fnmatch
from setuptools import find_packages, setup, Extension
from setuptools.command.build_py import build_py as build_py_orig
from Cython.Build import cythonize


extensions = [
# example of extensions with regex
Extension('spam.*', ['spam/*.py']),
# example of extension with single source file
Extension('spam.fizz.buzz', ['spam/fizz/buzz.py']),
]


class build_py(build_py_orig):
def build_packages(self):
pass


setup(
name='...',
version='...',
packages=find_packages(),
ext_modules=cythonize(extensions),
cmdclass={'build_py': build_py},
)

复杂情况:将 cythonized 扩展与源模块混合

如果你想只编译选定的模块并保持其余部分不变,你将需要更复杂的逻辑;在这种情况下,您需要覆盖模块查找。在下面的示例中,我仍然将 spam.baconspam.eggsspam.fizz.buzz 编译为共享对象,但保留了 __init__.py 文件未被修改,因此它们将作为源模块包含在内:

import fnmatch
from setuptools import find_packages, setup, Extension
from setuptools.command.build_py import build_py as build_py_orig
from Cython.Build import cythonize


extensions = [
Extension('spam.*', ['spam/*.py']),
Extension('spam.fizz.buzz', ['spam/fizz/buzz.py']),
]
cython_excludes = ['**/__init__.py']


def not_cythonized(tup):
(package, module, filepath) = tup
return any(
fnmatch.fnmatchcase(filepath, pat=pattern) for pattern in cython_excludes
) or not any(
fnmatch.fnmatchcase(filepath, pat=pattern)
for ext in extensions
for pattern in ext.sources
)


class build_py(build_py_orig):
def find_modules(self):
modules = super().find_modules()
return list(filter(not_cythonized, modules))

def find_package_modules(self, package, package_dir):
modules = super().find_package_modules(package, package_dir)
return list(filter(not_cythonized, modules))


setup(
name='...',
version='...',
packages=find_packages(),
ext_modules=cythonize(extensions, exclude=cython_excludes),
cmdclass={'build_py': build_py},
)

关于python - 只打包用 Cython 编译的 python 库的二进制编译 .so 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50570967/

43 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com