文章目录
- 一、简介
- 1.1、Python中的文件类型:.py + .pyc + .pyd
- 1.2、基本原理
- 1.2.1、函数详解:Extension() —— 用于定义扩展模块(C/C++ 扩展)的类
- 1.2.2、函数详解:setup() —— 用于配置和构建包的函数
- 二、构建过程
- 2.0、环境配置
- 2.1、第一步:构建 setup.py 脚本(需手动指定)
- 2.1.1、构建 setup.py 脚本(方式一):基于 setup() 函数
- 2.1.2、构建 setup.py 脚本(方式二):基于 Extension() + setup() 函数
- 2.2、第二步:将 .py 文件编译成 .pyd 文件:python setup.py build_ext --inplace
- 2.3、pyd 模块的导入与使用
- 四、(批量)将 .py 文件编译成 .pyd 文件(自动执行)
一、简介
1.1、Python中的文件类型:.py + .pyc + .pyd
py是明文且易读,pyc不易读但易被反编译,pyd不易读且不易被反编译(用于代码加密)。
源代码文件 (.py)
:Python 的源代码文件以 .py 为扩展名,其中包含了 Python 代码的文本内容。这些文件可以直接编辑,并且可以通过 Python 解释器执行。字节码文件 (.pyc)
:当 Python 的源代码文件被导入时,Python 解释器会将其编译成字节码,并将字节码保存在以 .pyc 为扩展名的文件中。这些文件是 Python 解释器优化后的版本,用于加快导入模块的速度。如果 Python 源代码文件发生了改变,Python 解释器会自动重新生成对应的 .pyc 文件。
- 使用 py_compile 将 .py 文件编译成 .pyc 文件:
python -m py_compile xxx.py
- 特点:难以阅读,但很容易被反编译工具反编译回可读的源代码(如: uncompyle6)。
动态链接库 (.pyd)
:在 Windows 平台上,Python 的扩展模块通常以 .pyd 为扩展名,它们是用 C 或 C++ 编写的,并且提供了对其他语言编写的库和系统调用的接口。.pyd 文件通常包含了用 C 或 C++ 编写的扩展模块,它们可以通过 Python 解释器导入,并且提供了对其它语言编写的库和系统调用的接口。
- 使用 Cython 将 .py 文件编译成 C 扩展模块,从而生成二进制 .pyc 文件:
python setup.py build_ext
- 特点:提高了反编译的难度。但反汇编工具仍可以对二进制文件进行逆向工程,尽管难度更大。编译过程复杂,可能需要配置额外的工具链。
1.2、基本原理
- 使用函数 Extension() + setup() 构建
setup.py
扩展模块- 使用命令 python setup.py build_ext --inplace 生成扩展模块文件
Windows平台下,生成 .pyd 文件
Unix/Linux平台下,生成 .so 文件
1.2.1、函数详解:Extension() —— 用于定义扩展模块(C/C++ 扩展)的类
"""#########################################################################
# 函数功能:Python 中用于定义扩展模块(C/C++ 扩展)的类。
# ———— 通过 Extension 类,可以指定扩展模块的名称、源文件的路径、编译选项等信息,从而在构建扩展模块时提供必要的配置。
# 函数说明:Extension(name, sources, include_dirs=None, define_macros=None, undef_macros=None,
# library_dirs=None, libraries=None, runtime_library_dirs=None,
# extra_objects=None, extra_compile_args=None, extra_link_args=None, export_symbols=None,
# swig_opts=None, depends=None, language=None)
# 参数说明:
# - name: 扩展模块的名称,即在 Python 中导入时使用的名称。
# - sources: 源文件的路径列表,可以是 .c、.cpp、.pyx 等类型的文件。
# include_dirs: 包含的头文件目录列表。
# define_macros: 宏定义列表,例如 [(<macro_name>, <macro_value>)]。
# undef_macros: 要取消定义的宏列表。
# library_dirs: 库文件目录列表。
# libraries: 要链接的库文件列表。
# runtime_library_dirs:运行时库文件目录列表。
# extra_objects: 额外的目标文件列表。
# extra_compile_args:额外的编译选项列表。
# extra_link_args:额外的链接选项列表。
# export_symbols: 导出的符号列表。
# swig_opts: 用于 SWIG 扩展的额外选项。
# depends: 依赖的文件列表。
# language: 源文件的编程语言,例如 'c' 或 'c++'。
#########################################################################"""
1.2.2、函数详解:setup() —— 用于配置和构建包的函数
"""#########################################################################
# 函数功能:Python 中用于配置和构建包的函数,通常在 setup.py 脚本中使用。
# ———— 通过 setup 函数,可以指定包的名称、版本、作者信息、依赖关系、入口点等信息,并配置构建和安装过程。
# 函数说明:setup(**attrs)
# 参数说明:
# attrs:一个字典,包含了配置信息和选项。常用的参数包括:
# - name:包的名称。
# version:包的版本号。
# author:包的作者名称。
# author_email:作者的电子邮箱地址。
# description:包的简要描述。
# long_description:包的详细描述。
# packages:包含的子包列表。
# install_requires:依赖的其他包列表。
# entry_points:包的入口点。
# - ext_modules:扩展模块列表。
# package_data:包含的数据文件列表。
# scripts:可执行脚本文件列表。
# classifiers:包的分类列表。
# license:包的许可证。
#########################################################################"""
二、构建过程
2.0、环境配置
pip install python
pip install cython
2.1、第一步:构建 setup.py 脚本(需手动指定)
- 报错提示:
error: unknown file type '.py' (from 'F:\py\sample.py')
- 原因分析:试图将 Python 源文件直接传递给 Extension 对象,而它期望的是 C/C++ 源文件。
- 解决方案:使用 Cython 的 cythonize() 函数,将 Python 文件转换为 C/C++ 扩展模块,然后再将其传递给 Extension 对象。
2.1.1、构建 setup.py 脚本(方式一):基于 setup() 函数
from setuptools import setup
from Cython.Build import cythonize
setup(name='my_module', ext_modules=cythonize(r'F:\py\temp\my_module.py'))
2.1.2、构建 setup.py 脚本(方式二):基于 Extension() + setup() 函数
from setuptools import setup, Extension
from Cython.Build import cythonize
# 定义(单个)扩展模块
ext_modules = Extension(name="my_module", sources=[r"F:\py\temp\my_module.py"])
"""
# 定义(多个)扩展模块
ext_modules = [
Extension(name="my_module1", sources=[r"F:\py\temp\my_module1.py"]),
Extension(name="my_module2", sources=[r"F:\py\temp\my_module2.py"]),
# 添加更多的扩展模块...
]
"""
# 使用 cythonize() 函数将扩展模块编译为 C/C++ 扩展模块,并将其传递给 ext_modules 参数。
setup(ext_modules=cythonize(ext_modules))
2.2、第二步:将 .py 文件编译成 .pyd 文件:python setup.py build_ext --inplace
在命令行中,执行命令:
python setup.py build_ext --inplace
python
: Python 解释器可执行文件的路径(默认使用环境变量的配置路径)。setup.py
:Python 脚本文件,用于定义 Python 包的构建和安装方式。通常,这个文件会包含一些元数据和构建指令,以告诉 setuptools(Python 的一个包管理工具)如何构建和安装这个包。python setup.py
:使用 python 命令,执行 Python 脚本文件。build_ext
: Python 定义的一个命令,用于构建扩展模块。--inplace
:可选。表示编译后的 .pyd 文件保存在默认的构建目录中,复制一份到当前目录中。
- 默认路径:
build\lib.win-amd64-cpython-39\my_module.cp39-win_amd64.pyd
编译后,将在当前路径下生成以下文件:
- (1)
build
文件夹:包含编译过程中的所有中间文件和最终的扩展模块库文件。- (2)使用 Cython 工具,将 .py 文件编译成 .c 文件:
my_module.c
- (3)编译后生成二进制 .pyc 文件:
my_module.pyd
2.3、pyd 模块的导入与使用
# my_module.py
def hello():
return "Hello, World!"
def add(a, b):
return a + b
def subtract(a, b):
return a - b
同一个文件的py与pyd不要保存在一个路径下,否则无法区别是调用的py模块还是pyd模块。
# main.py
import my_module # 导入pyd模块
print(my_module.hello()) # 输出: Hello, World!
print(my_module.add(1, 2)) # 输出: 3
print(my_module.subtract(1, 2)) # 输出: -1
四、(批量)将 .py 文件编译成 .pyd 文件(自动执行)
将批量 .py 文件编译成 .pyd 文件。
"""##############################################################
# 功能:将批量.py文件编译成.pyd文件。
##############################################################"""
import os
import glob
import shutil
def py2pyd(one_all, del_py, paths):
def compile_single_py(path):
folder_path = os.path.dirname(path) # 获取文件夹路径
file_path = os.path.split(path)[1] # 获取文件名(包括后缀)
os.chdir(folder_path) # 改变当前工作目录为 folder_path 所指定的目录
#############################################################################
# 在在当前路径下,自动生成一个setup.py文件
with open('setup.py', 'w') as f:
f.write('from setuptools import setup\n')
f.write('from Cython.Build import cythonize\n')
f.write(f"setup(name='test', ext_modules=cythonize('{file_path}'))")
# 开始编译setup.py(编译结束,将在当前路径下生成:xxx.cp38-win_amd64.pyd + xxx.c + build文件夹)
os.system('python setup.py build_ext --inplace')
#############################################################################
filename = file_path.split('.py')[0] # 获取文件名(没有后缀)
pyd_name = f'{folder_path}\\{filename}.pyd' # 获取.pyd文件路径
# 若在当前路径下,已存在同名的.pyd文件,则删除。
if os.path.exists(pyd_name):
os.remove(pyd_name)
"""修改.pyd文件名"""
# 获取.pyd文件的文件名 ———— 如: xxx.cp38-win_amd64.pyd
amd64_pyd = glob.glob(filename + "*.pyd") # glob.glob: 获取指定模式下的所有文件名列表
print("*"*100)
print(f"{amd64_pyd[0]} has been generated.")
# 修改.pyd文件的文件名 ———— 如: xxx.pyd,即删除冗余的.cp38-win_amd64
os.rename(amd64_pyd[0], pyd_name) # os.rename(old_name, new_name): 将old_name重命名为new_name.
"""删除过程文件"""
os.remove('setup.py') # 删除自动生成的setup.py文件
os.remove(f'{filename}.c') # 删除自动生成的xxx.c文件
shutil.rmtree(os.path.join(folder_path, 'build')) # 删除自动生成的build文件夹及其所有内容。
"""删除.py源文件 ———— 无法恢复,慎用!"""
if del_py == 'del':
os.remove(file_path)
def get_all_file(path):
"""遍历给定目录下的所有.py文件,包含子目录里的.py文件"""
for root, dirs, files in os.walk(path):
for name in files:
if name.endswith(".py"):
compile_single_py(os.path.join(root, name))
if one_all == 'one':
compile_single_py(paths) # 若为单个.py文件,则直接执行。
else:
get_all_file(paths) # 若为多个.py文件,则遍历所有文件,并单个执行。
"""##############################################################
# 参数说明:
# all: 批量编译指定目录及其子目录下的所有.py文件
# one: 编译单个.py文件
# del: 编译完成后删除源文件。注意:无法恢复,谨慎使用!
# nodel: 编译完成后保留源文件。
##############################################################"""
"""##############################################################
# 示例1:批量编译指定目录及其子目录下的所有.py文件
# py2pyd('all', 'nodel', r'D:\\PYTHON\\toPYD\\test') # 不删除源文件
# py2pyd('all', 'del', r'D:\\PYTHON\\toPYD\\test') # 删除源文件
#
# 示例2:编译单个.py文件
# py2pyd('one', 'nodel', r'D:\\PYTHON\\toPYD\\test\\mycode.py') # 不删除源文件
# py2pyd('one', 'del', r'D:\\PYTHON\\toPYD\\test\\mycode.py') # 删除源文件
##############################################################"""
if __name__ == '__main__':
try:
from setuptools import setup, Extension
from Cython.Build import cythonize
py2pyd('one', 'nodel', r'F:\py\pyinstaller\test.py')
except ImportError:
print("cython模块未安装,安装命令:pip install cython")