PyInstaller& Nuitka & 项目 (如 django)

我来给你展示用 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. 完整对比示例

创建更实用的测试代码

calculator.py

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:构建脚本(混合构建)

build.py

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

四、优势总结

这种混合方案的优势:

  1. 安全性:核心算法和敏感配置用 Nuitka 编译保护
  2. 兼容性:Django 框架用 PyInstaller 打包,兼容性好
  3. 灵活性:开发环境用源码,生产环境用编译版本
  4. 性能:关键部分编译为机器码,性能更好
  5. 维护性:清晰的架构分离,便于维护

五、注意事项

  1. 测试要充分:在打包前充分测试所有功能
  2. 保留源码:永远保留一份源码用于维护
  3. 版本管理:记录每个版本的编译参数
  4. 备份机制:确保可以回退到源码版本
  5. 监控日志:生产环境增加详细的运行日志

这种方案是目前最优的 Django 项目保护方案,既保证了安全性,又维持了 Django 的动态特性兼容性。

相关推荐
飞梦工作室1 小时前
Spring Boot3 + Milvus2 实战:向量检索应用开发指南
java·spring boot·后端
weixin_462446231 小时前
使用 Python + Tkinter + openpyxl 实现 Excel 文本化转换
开发语言·python·excel
啦啦右一1 小时前
杂货铺 | TensorFlow GPU 无法识别问题
人工智能·python·tensorflow
Q_Q19632884751 小时前
python+django/flask+vue的高铁火车铁路订票管理系统
spring boot·python·django·flask·node.js·php
南极星10051 小时前
OPENCV(python)--初学之路(十一)
人工智能·python·opencv
feathered-feathered1 小时前
网络原理——应用层协议HTTP/HTTPS(重点较为突出)
java·网络·后端·网络协议·http·https
IT_陈寒1 小时前
React 18并发渲染实战:这5个性能陷阱让我浪费了整整一周!
前端·人工智能·后端
再__努力1点1 小时前
【50】OpenCV背景减法技术解析与实现
开发语言·图像处理·人工智能·python·opencv·算法·计算机视觉
AI 嗯啦1 小时前
Flask 框架基础介绍
后端·python·flask