我来给你展示用 Nuitka 和 PyInstaller 打包同一行简单 Python 代码的完整例子。
1. 首先创建测试代码
test_simple.py:
python
print("Hello from Python! 1 + 2 =", 1 + 2)
2. 使用 PyInstaller 打包
安装 PyInstaller:
bash
pip install pyinstaller
打包命令:
bash
# 最简单的打包方式(会产生很多文件)
pyinstaller test_simple.py
# 单文件打包(推荐)
pyinstaller --onefile test_simple.py
# 更简洁的单文件打包(无控制台窗口,如果不需要)
pyinstaller --onefile --noconsole test_simple.py
# 添加图标(如果有的话)
pyinstaller --onefile --icon=myicon.ico test_simple.py
运行打包后的程序:
bash
# 进入输出目录
cd dist
./test_simple # Linux/Mac
# 或
test_simple.exe # Windows
查看打包文件大小:
bash
# Linux/Mac
ls -lh dist/test_simple
# Windows
dir dist\test_simple.exe
PyInstaller 打包结果:通常 5-10 MB
3. 使用 Nuitka 打包
安装 Nuitka:
bash
pip install nuitka
基本打包命令:
bash
# 最简单的打包
nuitka test_simple.py
# 单文件打包(--standalone 表示独立运行)
nuitka --standalone test_simple.py
# 单文件模式(需要额外插件)
nuitka --standalone --onefile test_simple.py
# 优化编译选项
nuitka --standalone --onefile --enable-plugin=tk-inter --follow-imports test_simple.py
# Windows 下常用命令
nuitka --standalone --mingw64 --windows-disable-console test_simple.py
# Linux/Mac 下常用命令
nuitka --standalone --clang --lto test_simple.py
使用 Onefile 插件(推荐):
bash
# 先安装 onefile 插件
pip install nuitka-onefile
# 然后打包
nuitka --standalone --onefile test_simple.py
运行 Nuitka 打包的程序:
bash
# Nuitka 会生成一个目录(除非用 --onefile)
cd test_simple.dist
./test_simple # Linux/Mac
test_simple.exe # Windows
4. 完整对比示例
创建更实用的测试代码
python
def main():
print("简易计算器")
print("=" * 20)
try:
a = float(input("请输入第一个数字: "))
b = float(input("请输入第二个数字: "))
print(f"\n计算结果:")
print(f"{a} + {b} = {a + b}")
print(f"{a} - {b} = {a - b}")
print(f"{a} × {b} = {a * b}")
if b != 0:
print(f"{a} ÷ {b} = {a / b:.2f}")
else:
print(f"{a} ÷ {b} = 无穷大")
except ValueError:
print("错误:请输入有效的数字!")
except Exception as e:
print(f"发生错误:{e}")
if __name__ == "__main__":
main()
PyInstaller 打包脚本
build_pyinstaller.py:
python
import subprocess
import os
import time
def build_with_pyinstaller():
print("开始使用 PyInstaller 打包...")
start_time = time.time()
# 清理之前的构建文件
for folder in ['build', 'dist']:
if os.path.exists(folder):
import shutil
shutil.rmtree(folder)
# 执行打包命令
cmd = [
'pyinstaller',
'--onefile', # 单文件
'--name=CalculatorApp', # 指定输出名称
'--clean', # 清理临时文件
'calculator.py'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("PyInstaller 打包成功!")
exe_path = os.path.join('dist', 'CalculatorApp.exe')
if os.path.exists(exe_path):
size = os.path.getsize(exe_path) / (1024*1024) # MB
print(f"生成文件: {exe_path}")
print(f"文件大小: {size:.2f} MB")
else:
print("PyInstaller 打包失败!")
print("错误输出:", result.stderr)
end_time = time.time()
print(f"打包耗时: {end_time - start_time:.2f} 秒")
if __name__ == "__main__":
build_with_pyinstaller()
Nuitka 打包脚本
build_nuitka.py:
python
import subprocess
import os
import time
def build_with_nuitka():
print("开始使用 Nuitka 打包...")
start_time = time.time()
# 清理之前的构建文件
for item in ['CalculatorApp.build', 'CalculatorApp.dist', 'CalculatorApp.exe']:
if os.path.exists(item):
if os.path.isdir(item):
import shutil
shutil.rmtree(item)
else:
os.remove(item)
# 执行打包命令
cmd = [
'python', '-m', 'nuitka',
'--standalone', # 独立运行
'--onefile', # 单文件(需要 nuitka-onefile 插件)
'--output-dir=./build', # 输出目录
'--output-filename=CalculatorApp',
'--remove-output', # 清理中间文件
'--assume-yes-for-downloads',
'calculator.py'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("Nuitka 打包成功!")
exe_path = os.path.join('build', 'CalculatorApp.exe')
if os.path.exists(exe_path):
size = os.path.getsize(exe_path) / (1024*1024) # MB
print(f"生成文件: {exe_path}")
print(f"文件大小: {size:.2f} MB")
else:
print("Nuitka 打包失败!")
print("错误输出:", result.stderr)
end_time = time.time()
print(f"打包耗时: {end_time - start_time:.2f} 秒")
if __name__ == "__main__":
build_with_nuitka()
对比测试脚本
compare_builders.py:
python
import subprocess
import os
import time
def test_execution_speed():
"""测试两种打包方式的执行速度"""
print("测试执行速度...")
print("-" * 40)
tests = [
("PyInstaller", "dist/CalculatorApp.exe"),
("Nuitka", "build/CalculatorApp.exe")
]
for name, path in tests:
if os.path.exists(path):
print(f"\n测试 {name}:")
start_time = time.perf_counter()
# 运行程序(传入预设输入)
result = subprocess.run(
[path],
input="12\n3\n",
text=True,
capture_output=True,
timeout=5
)
end_time = time.perf_counter()
print(f"执行时间: {(end_time - start_time)*1000:.2f} ms")
print("输出:", result.stdout[:100] + "...")
else:
print(f"\n{name} 文件不存在: {path}")
def compare_file_sizes():
"""比较文件大小"""
print("\n\n比较文件大小:")
print("-" * 40)
files = [
("PyInstaller", "dist/CalculatorApp.exe"),
("Nuitka", "build/CalculatorApp.exe"),
("原始Python脚本", "calculator.py")
]
for name, path in files:
if os.path.exists(path):
size_bytes = os.path.getsize(path)
if size_bytes > 1024*1024: # MB
size = size_bytes / (1024*1024)
unit = "MB"
else: # KB
size = size_bytes / 1024
unit = "KB"
print(f"{name}: {size:.2f} {unit}")
if __name__ == "__main__":
print("PyInstaller vs Nuitka 打包对比测试")
print("=" * 50)
compare_file_sizes()
test_execution_speed()
5. 一键打包和比较
run_all.py:
python
#!/usr/bin/env python3
"""
一键运行所有打包和比较测试
"""
import subprocess
import sys
def main():
print("Python 打包工具对比测试")
print("=" * 50)
# 1. 首先确保有源代码
print("\n1. 检查源代码...")
with open("calculator.py", "w", encoding="utf-8") as f:
f.write("""def main():
print("简易计算器")
print("=" * 20)
try:
a = float(input("请输入第一个数字: "))
b = float(input("请输入第二个数字: "))
print(f"\\n计算结果:")
print(f"{a} + {b} = {a + b}")
print(f"{a} - {b} = {a - b}")
print(f"{a} × {b} = {a * b}")
if b != 0:
print(f"{a} ÷ {b} = {a / b:.2f}")
else:
print(f"{a} ÷ {b} = 无穷大")
except ValueError:
print("错误:请输入有效的数字!")
if __name__ == "__main__":
main()
""")
# 2. 安装必要的包
print("\n2. 安装必要的包...")
packages = ["pyinstaller", "nuitka"]
for package in packages:
print(f"正在安装 {package}...")
subprocess.run([sys.executable, "-m", "pip", "install", package, "-q"])
# 3. 使用 PyInstaller 打包
print("\n3. 使用 PyInstaller 打包...")
subprocess.run([
"pyinstaller",
"--onefile",
"--name=CalculatorApp",
"--clean",
"calculator.py"
])
# 4. 使用 Nuitka 打包
print("\n4. 使用 Nuitka 打包...")
subprocess.run([
sys.executable, "-m", "nuitka",
"--standalone",
"--onefile",
"--output-dir=./nuitka_build",
"--output-filename=CalculatorApp_nuitka",
"--remove-output",
"calculator.py"
])
# 5. 显示结果
print("\n5. 打包结果:")
print("-" * 40)
results = [
("PyInstaller", "dist/CalculatorApp.exe"),
("Nuitka", "nuitka_build/CalculatorApp_nuitka.exe")
]
for tool, path in results:
try:
import os
size = os.path.getsize(path)
print(f"{tool}: {path}")
print(f" 大小: {size/1024/1024:.2f} MB")
except:
print(f"{tool}: 文件未找到")
print("\n6. 测试运行...")
print("-" * 40)
# 测试运行
import time
test_input = "10\n2\n"
for tool, path in results:
try:
start = time.time()
result = subprocess.run(
[path],
input=test_input,
text=True,
capture_output=True,
timeout=3
)
elapsed = (time.time() - start) * 1000
print(f"\n{tool} 测试:")
print(f"启动时间: {elapsed:.1f} ms")
print("输出预览:", result.stdout[:80].replace("\n", " | "))
except Exception as e:
print(f"\n{tool} 测试失败: {e}")
if __name__ == "__main__":
main()
6. 实际运行命令示例
bash
# 创建一个干净的测试环境
mkdir test_package && cd test_package
# 创建最简单的 Python 文件
echo "print('Hello from packaged Python!')" > hello.py
# 1. PyInstaller 打包
pyinstaller --onefile hello.py
# 运行
./dist/hello # Linux/Mac
dist\hello.exe # Windows
# 2. Nuitka 打包
# 先安装 onefile 插件
pip install nuitka-onefile
# 打包
nuitka --standalone --onefile hello.py
# 运行
./hello.bin # Linux/Mac
hello.exe # Windows
# 3. 查看差异
ls -lh dist/hello* hello.bin # Linux/Mac
dir dist\hello.exe hello.exe # Windows
主要区别总结
| 特性 | PyInstaller | Nuitka |
|---|---|---|
| 原理 | 打包 Python 解释器+字节码 | 将 Python 编译为 C,再编译为二进制 |
| 文件大小 | 较大(5-10 MB) | 较小(3-8 MB) |
| 启动速度 | 较慢(需要解压) | 较快(直接执行) |
| 反编译难度 | 较易(字节码可反编译) | 较难(编译为机器码) |
| 兼容性 | 很好 | 较好(可能有库兼容问题) |
| 打包时间 | 快 | 慢(需要编译) |
推荐使用场景
- 快速打包:用 PyInstaller
- 性能要求高:用 Nuitka
- 防止反编译:用 Nuitka
- 库兼容性复杂:用 PyInstaller
- 跨平台发布:两者都可以,但 PyInstaller 更简单
最简单的测试就是创建一个只有一行代码的 Python 文件,然后用两种工具分别打包试试看!
####### DJANGO
最优方案:混合使用 Nuitka + PyInstaller + 源码
可以结合使用!这是目前最可行的方案。我来给你一个完整的最优解:
一、架构设计思路
┌─────────────────────────────────────────────┐
│ 最终分发包(PyInstaller) │
├─────────────────────────────────────────────┤
│ 1. 核心业务模块(Nuitka 编译为 .so/.dll) │
│ 2. 配置文件(加密或混淆) │
│ 3. Django 框架(源码 + PyInstaller 打包) │
│ 4. 静态资源(直接包含) │
└─────────────────────────────────────────────┘
二、具体实施方案
步骤1:项目结构改造
myproject/
├── src/ # 需要保护的源码
│ ├── core/ # 核心算法(用 Nuitka 编译)
│ │ ├── __init__.py
│ │ ├── business_logic.py # 重要业务逻辑
│ │ └── security.py # 安全相关
│ └── config_loader.py # 配置加载器(用 Nuitka 编译)
├── django_app/ # Django 应用(保持源码)
│ ├── settings.py
│ ├── urls.py
│ └── views.py
├── config/ # 配置文件
│ ├── sensitive_config.py # 敏感配置(用 Nuitka 编译)
│ └── public_config.py # 公开配置
├── build.py # 构建脚本
└── manage.py
步骤2:创建配置文件加密模块
config/sensitive_config.py:
python
# 重要:这个文件需要用 Nuitka 编译!
import base64
import hashlib
from cryptography.fernet import Fernet
class SensitiveConfig:
"""敏感配置类 - 这个文件用 Nuitka 编译保护"""
# 加密的配置数据(运行时解密)
_ENCRYPTED_CONFIGS = {
'database_password': b'gAAAAABl...加密后的数据...',
'api_secret_key': b'gAAAAABl...加密后的数据...',
'payment_gateway': b'gAAAAABl...加密后的数据...',
}
def __init__(self):
# 从环境变量或安全位置获取密钥
self._key = self._derive_key()
self._cipher = Fernet(self._key)
def _derive_key(self):
"""派生密钥(增加破解难度)"""
# 使用多个源生成密钥
seed = os.environ.get('CONFIG_KEY', 'default_seed')
machine_id = hashlib.sha256(str(uuid.getnode()).encode()).hexdigest()
combined = f"{seed}:{machine_id}"
# 多次哈希增加复杂度
for _ in range(10000):
combined = hashlib.sha512(combined.encode()).hexdigest()
return base64.urlsafe_b64encode(combined[:32].encode())
def get(self, key, default=None):
"""获取解密后的配置"""
if key in self._ENCRYPTED_CONFIGS:
try:
encrypted = self._ENCRYPTED_CONFIGS[key]
return self._cipher.decrypt(encrypted).decode()
except:
return default
return default
# 关键方法 - 防止反射攻击
def __getattribute__(self, name):
if name.startswith('_'):
return object.__getattribute__(self, name)
raise AttributeError("直接访问被禁止,请使用 get() 方法")
# 单例实例
sensitive_config = SensitiveConfig()
步骤3:核心业务模块
src/core/business_logic.py:
python
# 重要业务逻辑 - 用 Nuitka 编译
import time
import hashlib
class PaymentProcessor:
"""支付处理核心逻辑"""
def __init__(self, config_loader):
self.config = config_loader
def process_payment(self, amount, user_id):
"""处理支付 - 核心算法"""
# 使用加密的 API 密钥
secret = self.config.get('api_secret_key')
# 生成交易签名
timestamp = int(time.time())
sign_str = f"{amount}:{user_id}:{timestamp}:{secret}"
signature = hashlib.sha256(sign_str.encode()).hexdigest()
# 核心业务逻辑...
return {
'transaction_id': f"TXN{timestamp}{user_id}",
'signature': signature[:16],
'status': 'processing'
}
def validate_signature(self, data, signature):
"""验证签名"""
# 防逆向的逻辑
if self._anti_debug_check():
return False
# 实际验证逻辑...
return True
def _anti_debug_check(self):
"""反调试检查"""
import sys
# 检查是否在调试器中运行
if hasattr(sys, 'gettrace') and sys.gettrace() is not None:
return True
# 检查运行时间(防止单步调试)
start_time = time.time()
# 执行一些计算
for i in range(1000):
_ = hashlib.sha256(str(i).encode()).hexdigest()
elapsed = time.time() - start_time
# 如果执行时间异常,可能在被调试
if elapsed > 0.1: # 正常应该很快
return True
return False
步骤4:构建脚本(混合构建)
python
#!/usr/bin/env python3
"""
混合构建脚本:Nuitka 编译核心模块 + PyInstaller 打包整个应用
"""
import os
import sys
import shutil
import subprocess
import tempfile
from pathlib import Path
class HybridBuilder:
def __init__(self):
self.project_root = Path(__file__).parent
self.build_dir = self.project_root / "build"
self.dist_dir = self.project_root / "dist"
def clean_build(self):
"""清理构建目录"""
for dir_path in [self.build_dir, self.dist_dir]:
if dir_path.exists():
shutil.rmtree(dir_path)
self.build_dir.mkdir(exist_ok=True)
self.dist_dir.mkdir(exist_ok=True)
def compile_with_nuitka(self):
"""用 Nuitka 编译核心模块"""
print("步骤1: 使用 Nuitka 编译核心模块...")
modules_to_compile = [
{
'module': 'src.core.business_logic',
'output': 'business_logic',
'options': ['--module', '--output-dir=compiled_core']
},
{
'module': 'src.core.security',
'output': 'security',
'options': ['--module', '--output-dir=compiled_core']
},
{
'module': 'config.sensitive_config',
'output': 'sensitive_config',
'options': [
'--module',
'--output-dir=compiled_config',
'--onefile',
'--include-package=cryptography'
]
}
]
for module_info in modules_to_compile:
print(f"编译模块: {module_info['module']}")
cmd = [
sys.executable, '-m', 'nuitka',
*module_info['options'],
f'--output-filename={module_info["output"]}',
'--remove-output',
'--lto',
'--assume-yes-for-downloads',
]
# 添加反逆向选项
cmd.extend([
'--enable-plugin=anti-bloat',
'--noinclude-ipython-mode=nofollow',
'--nofollow-import-to=tkinter',
'--nofollow-import-to=unittest',
'--nofollow-import-to=debugpy',
])
# 如果 Windows,添加更多保护
if sys.platform == 'win32':
cmd.extend([
'--windows-disable-console',
'--windows-icon-from-ico=icon.ico',
])
cmd.append(str(self.project_root / module_info['module'].replace('.', '/')))
cmd[-1] += '.py'
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"编译失败: {module_info['module']}")
print(result.stderr)
return False
print("Nuitka 编译完成!")
return True
def create_stub_modules(self):
"""创建桩模块,用于在开发环境导入"""
print("步骤2: 创建开发环境桩模块...")
stub_dir = self.project_root / "stubs"
stub_dir.mkdir(exist_ok=True)
# 为编译模块创建桩
stub_content = '''
"""
开发环境桩模块
在生产环境中,此文件会被编译后的模块替换
"""
import warnings
import sys
class StubModule:
def __init__(self, module_name):
self.__name__ = module_name
def __getattr__(self, name):
warnings.warn(
f"正在使用桩模块 {{self.__name__}},请在生产环境使用编译版本",
RuntimeWarning
)
# 尝试从源码导入(开发环境)
try:
import importlib
# 移除 'stubs.' 前缀
real_module_name = self.__name__.replace('stubs.', '')
module = importlib.import_module(real_module_name)
return getattr(module, name)
except ImportError:
raise AttributeError(
f"模块 '{{self.__name__}}' 没有属性 '{{name}}'"
)
# 替换 sys.modules 中的模块
def create_stub(module_name):
stub = StubModule(module_name)
sys.modules[module_name] = stub
return stub
# 创建桩实例
business_logic = create_stub('stubs.business_logic')
security = create_stub('stubs.security')
sensitive_config = create_stub('stubs.sensitive_config')
'''
(stub_dir / "__init__.py").write_text(stub_content)
print("桩模块创建完成!")
def package_with_pyinstaller(self):
"""用 PyInstaller 打包整个应用"""
print("步骤3: 使用 PyInstaller 打包 Django 应用...")
# 创建 spec 文件
spec_content = f'''
# -*- mode: python ; coding: utf-8 -*-
import sys
import os
from PyInstaller.utils.hooks import collect_all, collect_data_files
from PyInstaller.__main__ import run
# 项目路径
project_root = r"{self.project_root}"
# 添加编译后的模块
additional_imports = [
'compiled_core.business_logic',
'compiled_core.security',
'compiled_config.sensitive_config',
]
# 分析 Django 相关模块
django_hidden_imports = [
'django.core.management',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.template.context_processors',
'asgiref',
'sqlparse',
'pytz',
]
# 收集所有数据文件
datas = []
datas.extend(collect_data_files('django'))
datas.append(('templates/*', 'templates'))
datas.append(('static/*', 'static'))
datas.append(('media/*', 'media'))
# 如果是 Windows,添加 icon
if sys.platform == 'win32':
icon = os.path.join(project_root, 'icon.ico')
else:
icon = None
a = Analysis(
['manage.py'],
pathex=[project_root],
binaries=[],
datas=datas,
hiddenimports=django_hidden_imports + additional_imports,
hookspath=[],
hooksconfig={{}},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=1,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='my_django_app',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=icon,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='my_django_app',
)
'''
spec_file = self.build_dir / "django_app.spec"
spec_file.write_text(spec_content)
# 运行 PyInstaller
cmd = [
'pyinstaller',
'--clean',
'--distpath', str(self.dist_dir),
'--workpath', str(self.build_dir / 'pyinstaller'),
'--specpath', str(self.build_dir),
str(spec_file)
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("PyInstaller 打包完成!")
return True
else:
print("PyInstaller 打包失败!")
print(result.stderr)
return False
def create_production_loader(self):
"""创建生产环境加载器"""
print("步骤4: 创建生产环境加载器...")
loader_content = '''#!/usr/bin/env python3
"""
生产环境加载器 - 确保正确加载编译模块
"""
import os
import sys
import warnings
def setup_environment():
"""设置运行环境"""
# 检测是否在打包环境中
is_frozen = getattr(sys, 'frozen', False)
if is_frozen:
# PyInstaller 打包环境
base_path = sys._MEIPASS
# 添加编译模块路径
compiled_paths = [
os.path.join(base_path, 'compiled_core'),
os.path.join(base_path, 'compiled_config'),
]
for path in compiled_paths:
if os.path.exists(path):
sys.path.insert(0, path)
else:
# 开发环境
base_path = os.path.dirname(os.path.abspath(__file__))
# 优先使用编译模块(如果存在)
compiled_path = os.path.join(base_path, 'compiled_core')
if os.path.exists(compiled_path):
sys.path.insert(0, compiled_path)
# 设置 Django 环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_app.settings')
# 禁用开发警告
warnings.filterwarnings('ignore', category=RuntimeWarning)
return base_path
def patch_django_for_compiled_modules():
"""修补 Django 以支持编译模块"""
import django
import importlib
original_import_module = importlib.import_module
def patched_import_module(name, package=None):
try:
# 首先尝试正常导入
return original_import_module(name, package)
except ImportError:
# 如果是我们的编译模块,尝试从编译路径导入
if name in ['business_logic', 'security', 'sensitive_config']:
# 尝试不同的导入路径
for prefix in ['compiled_core.', 'compiled_config.', '']:
try:
return original_import_module(prefix + name)
except ImportError:
continue
raise
# 应用补丁
importlib.import_module = patched_import_module
# 也需要修补 __import__
original_import = __import__
def patched_import(name, *args, **kwargs):
try:
return original_import(name, *args, **kwargs)
except ImportError:
if name in ['business_logic', 'security', 'sensitive_config']:
for prefix in ['compiled_core.', 'compiled_config.', '']:
try:
return original_import(prefix + name, *args, **kwargs)
except ImportError:
continue
raise
__builtins__['__import__'] = patched_import
if __name__ == '__main__':
# 设置环境
base_path = setup_environment()
# 修补导入系统
patch_django_for_compiled_modules()
# 导入并运行 Django
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"无法导入 Django。请确保已安装并配置正确。"
) from exc
# 运行 Django
execute_from_command_line(sys.argv)
'''
loader_file = self.dist_dir / "run_django.py"
loader_file.write_text(loader_content)
# 使可执行
if sys.platform != 'win32':
os.chmod(loader_file, 0o755)
print("生产环境加载器创建完成!")
def build(self):
"""执行完整构建流程"""
print("开始混合构建 Django 应用...")
print("=" * 50)
try:
self.clean_build()
# 1. 编译核心模块
if not self.compile_with_nuitka():
return False
# 2. 创建桩模块(可选)
self.create_stub_modules()
# 3. 打包整个应用
if not self.package_with_pyinstaller():
return False
# 4. 创建加载器
self.create_production_loader()
print("\n" + "=" * 50)
print("构建成功完成!")
print(f"输出目录: {self.dist_dir}")
print("\n运行命令:")
if sys.platform == 'win32':
print(f" {self.dist_dir}\\my_django_app.exe runserver")
else:
print(f" {self.dist_dir}/my_django_app runserver")
print("\n或使用加载器:")
if sys.platform == 'win32':
print(f" python {self.dist_dir}\\run_django.py runserver")
else:
print(f" python {self.dist_dir}/run_django.py runserver")
return True
except Exception as e:
print(f"构建过程中发生错误: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == '__main__':
builder = HybridBuilder()
success = builder.build()
if not success:
sys.exit(1)
步骤5:Django 项目中使用的适配代码
django_app/views.py:
python
# Django 视图 - 使用混合模式导入
import sys
def get_business_logic():
"""获取业务逻辑模块(支持开发和编译环境)"""
try:
# 首先尝试导入编译版本
from compiled_core import business_logic as compiled_logic
return compiled_logic
except ImportError:
try:
# 尝试导入开发版本
from src.core import business_logic as dev_logic
import warnings
warnings.warn("使用开发版本业务逻辑,请在生产环境使用编译版本")
return dev_logic
except ImportError:
# 最后尝试桩模块
from stubs import business_logic as stub_logic
return stub_logic
def get_config():
"""获取配置模块"""
try:
from compiled_config import sensitive_config as compiled_config
return compiled_config.sensitive_config
except ImportError:
try:
from config import sensitive_config as dev_config
return dev_config.sensitive_config
except ImportError:
from stubs import sensitive_config as stub_config
return stub_config
# 获取模块实例
business_logic = get_business_logic()
config = get_config()
# 在视图中使用
def payment_view(request):
"""支付视图"""
processor = business_logic.PaymentProcessor(config)
result = processor.process_payment(
amount=request.POST.get('amount'),
user_id=request.user.id
)
return JsonResponse(result)
三、使用和部署
构建命令:
bash
# 安装依赖
pip install nuitka pyinstaller cryptography django
# 运行构建
python build.py
# 或者分步构建
python -m nuitka --module src.core.business_logic --output-dir=compiled_core
python -m nuitka --module config.sensitive_config --output-dir=compiled_config
pyinstaller --name=my_django_app manage.py
运行应用:
bash
# 生产环境
cd dist
./my_django_app runserver # 或 ./run_django.py runserver
# 开发环境(使用源码)
python manage.py runserver
四、优势总结
这种混合方案的优势:
- 安全性:核心算法和敏感配置用 Nuitka 编译保护
- 兼容性:Django 框架用 PyInstaller 打包,兼容性好
- 灵活性:开发环境用源码,生产环境用编译版本
- 性能:关键部分编译为机器码,性能更好
- 维护性:清晰的架构分离,便于维护
五、注意事项
- 测试要充分:在打包前充分测试所有功能
- 保留源码:永远保留一份源码用于维护
- 版本管理:记录每个版本的编译参数
- 备份机制:确保可以回退到源码版本
- 监控日志:生产环境增加详细的运行日志
这种方案是目前最优的 Django 项目保护方案,既保证了安全性,又维持了 Django 的动态特性兼容性。