The method of packaging Python code into. so

Prerequisite: .

  • Ensure that gcc is installed on the computer and that the terminal can retrieve it
  • Ensure that the cython package is installed in Python. If not, use pip install cython for installation first

Packaging method: .

  • Step 1: Write the compilation script setup.py, with the following code:

    # encoding = utf-8
    from distutils.core import setup
    from Cython.Build import cythonize
    setup(
    name = 'libname',
    ext_modules = cythonize(["file1.py", "file2.py", 	"dir/file3.py"], language_level = "3")
    )
    
  • Step 2: Execute in the terminal:

    python3 setup.py build_ext
    

Points to note: .

  1. ext_modules in setup() is not exe_modules, so don't make a mistake. If you make a mistake, you will find that it has only been compiled into a .c file and not used to generate .so
  2. The generated .so can only be used in the same system, platform, and Python version environment as the packaging computer. To use it on other platforms or environments, it needs to be repackaged on the specified platform or environment
  3. When there are multiple versions of Python, such as the coexistence of bloggers Python 2 and Python 3 on Linux, it is necessary to specify the Python version used for compilation, otherwise the terminal will alarm FutureWarning: Python directive 'language'_Level 'not set, using 2 for now (Py2). There are two ways to specify a version:
    • Method 1 Write # cython:language_level=3 at the header of each py file to be packaged
    • Method 2 : Like in setup.py above, add the language_level = "3" option to the cythonize function
  4. The structure of the packaged .so file will be disrupted, such as the file3.so generated by file3.py in dir being in the same directory as the other two, instead of being in the dir folder. If not adjusted, there may be import errors when executing the script, so it is necessary to manually adjust the file structure

Attached code for batch packaging:


import os
import sys
import shutil
import numpy
import tempfile
from setuptools import setup
from setuptools.extension import Extension
from Cython.Build import cythonize
from Cython.Distutils import build_ext
import platform
# code from:   https://blog.csdn.net/qq_33375598/article/details/118677130
#directory of files stored after construction 
build_root_dir = 'build/lib.' + platform.system().lower() + '-' + platform.machine() + '-' + str(
sys.version_info.major) + '.' + str(sys.version_info.minor)
print(build_root_dir)
extensions = []
ignore_folders = ['build', 'test', 'tests']
conf_folders = ['conf']
def get_root_path(root):
if os.path.dirname(root) in ['', '.']: #obtain the file path for the file 
  return os.path.basename(root) #return path the final file name 
else:
  return get_root_path(os.path.dirname(root))
def copy_file(src, dest):
if os.path.exists(dest): #destination file exists and returns 
  return
if not os.path.exists(os.path.dirname(dest)): #the destination folder does not exist, recursively create a folder 
  os.makedirs(os.path.dirname(dest))
if os.path.isdir(src): #determine whether a certain path is a directory 
  shutil.copytree(src, dest) #copy the entire folder (the destination folder needs to not exist, otherwise it will fail) 
else:
  shutil.copyfile(src, dest) #copy the entire file 
def touch_init_file(): #create in temporary folder init
init_file_name = os.path.join(tempfile.mkdtemp(), '__init__.py')
with open(init_file_name, 'w'):
  pass
return init_file_name
init_file = touch_init_file()
print(init_file)
def compose_extensions(root='.'):
for file_in os.listdir(root): #all files in the current directory 
  abs_file = os.path.join(root, file_) #path splicing 
  if os.path.isfile(abs_file):
      if abs_file.endswith('.py'):
          extensions.append(Extension(get_root_path(abs_file) + '.*', [abs_file]))
      elif abs_file.endswith('.c') or abs_file.endswith('.pyc'):
          continue
      else:
          copy_file(abs_file, os.path.join(build_root_dir, abs_file))
      if abs_file.endswith('__init__.py'): #use blank __init__.py replace the original 
          copy_file(init_file, os.path.join(build_root_dir, abs_file))
  else:
      if os.path.basename(abs_file) in ignore_folders: #ignored files are not copied 
          continue
      if os.path.basename(abs_file) in conf_folders: #copy configuration files together 
          copy_file(abs_file, os.path.join(build_root_dir, abs_file))
      compose_extensions(abs_file)
compose_extensions()
os.remove(init_file)
setup(
name='your_project_name',
version='1.0',
ext_modules=cythonize(
  extensions,
  nthreads=16,
  compiler_directives=dict(always_allow_keywords=True),
  include_path=[numpy.get_include()], language_level="3"),
cmdclass=dict(build_ext=build_ext))

Reference link: .

Related articles