gl-py2pyd 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: gl-py2pyd
3
+ Version: 0.1.0
4
+ Summary: 将Python文件转换为pyd/so文件的工具
5
+ Home-page: https://github.com/yourusername/py2pyd-arg
6
+ Author: gu lei
7
+ Author-email:
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: cython
14
+ Requires-Dist: loguru
15
+ Dynamic: author
16
+ Dynamic: classifier
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: requires-dist
21
+ Dynamic: requires-python
22
+ Dynamic: summary
23
+
24
+ # Python转pyd/so工具
25
+
26
+ 这个工具用于将Python文件(.py)转换为编译后的Python扩展模块(.pyd或.so),使用Cython进行编译。
27
+
28
+ ## 功能特点
29
+
30
+ - 支持单个Python文件的转换
31
+ - 支持目录批量转换
32
+ - 支持递归处理子目录
33
+ - 转换后可选择是否删除原始Python文件
34
+ - 保留`__init__.py`文件内容处理
35
+ - 自动检测依赖并提供错误处理
36
+ - 针对不同操作系统自动选择正确的扩展名(Windows为.pyd,Linux/MacOS为.so)
37
+ - 优化编译选项,消除常见警告信息
38
+
39
+ ## 安装依赖
40
+
41
+ 在使用此工具前,请确保已安装以下依赖:
42
+
43
+ ```bash
44
+ pip install cython loguru
45
+ ```
46
+
47
+ 此外,你的系统需要有合适的C/C++编译器:
48
+ - Windows: 需要安装Visual C++ Build Tools
49
+ - Linux: 需要安装GCC
50
+ - macOS: 需要安装XCode命令行工具(`xcode-select --install`)
51
+
52
+ ## 使用方法
53
+
54
+ ### 1. 转换单个文件
55
+
56
+ ```bash
57
+ python py2pyd.py path/to/your/file.py
58
+ ```
59
+
60
+ ### 2. 转换目录下所有Python文件
61
+
62
+ ```bash
63
+ python py2pyd.py path/to/your/directory
64
+ ```
65
+
66
+ ### 3. 递归转换目录及其子目录中的所有Python文件
67
+
68
+ ```bash
69
+ python py2pyd.py -r path/to/your/directory
70
+ ```
71
+
72
+ ### 4. 转换后删除原始Python文件
73
+
74
+ ```bash
75
+ python py2pyd.py --remove path/to/your/file.py
76
+ ```
77
+
78
+ ### 5. 全部选项组合
79
+
80
+ ```bash
81
+ python py2pyd.py -r --remove path/to/your/directory
82
+ ```
83
+
84
+ ## 作为模块导入
85
+
86
+ 你也可以在自己的Python代码中导入该模块:
87
+
88
+ ```python
89
+ # 转换单个文件
90
+ from module.single_py2pyd import py2pyd
91
+ success = py2pyd('path/to/your/file.py')
92
+ if success:
93
+ print("转换成功")
94
+ else:
95
+ print("转换失败")
96
+
97
+ # 转换整个目录
98
+ from module.fileConversion import FileConversion
99
+ converter = FileConversion()
100
+ success = converter.get_all_file('path/to/your/directory', need_remove=False)
101
+ print(f"成功:{converter.success_count} 个文件,失败:{converter.fail_count} 个文件")
102
+ ```
103
+
104
+ ## 错误处理
105
+
106
+ 工具会自动检测必要的依赖并提供错误信息。常见问题包括:
107
+
108
+ 1. **缺少Cython**: 运行前会检查Cython是否已安装,若未安装则会提示安装。
109
+ 2. **编译失败**: 如果编译失败,会提供详细错误信息,可能是缺少编译器或者Python文件内容有问题。
110
+
111
+ ## 优化的编译选项
112
+
113
+ 本工具针对不同操作系统和环境进行了编译选项优化:
114
+
115
+ 1. **设置语言级别**: 显式设置Cython的`language_level`为3,避免语言级别警告
116
+ 2. **禁止无用代码警告**: 添加适当的编译选项(`-Wno-unreachable-code`)以禁止Cython生成的C代码中的不可达代码警告
117
+ 3. **修复Conda/Miniconda链接警告**: 在Conda环境中自动调整链接选项,避免重复的rpath警告
118
+
119
+ ## 注意事项
120
+
121
+ - 确保已安装Cython和适当的编译器
122
+ - 转换后的so/pyd文件会保留在原始Python文件的相同目录中
123
+ - 在Windows系统上生成.pyd文件,在Linux/MacOS上生成.so文件
124
+ - 对于复杂的Python项目,可能需要额外的编译选项
@@ -0,0 +1,9 @@
1
+ py2pyd.py,sha256=WBkiwqHMOhegcp0s57Zvh0i64SO3IiUgUwN4ej_lJhs,3016
2
+ module/__init__.py,sha256=Fh4A481uGhDkVmqGIH8MKH5_yAMncOz2UBdbCoYLl6k,220
3
+ module/fileConversion.py,sha256=C5N_8WkCadZFEo1ylbaGEiDoINK3lKh7bEbz_rqdu3Q,1714
4
+ module/single_py2pyd.py,sha256=lx1CojipLdkyJWSCsxSFHGNZPHCZKiN42Osd6Zmlktg,6048
5
+ gl_py2pyd-0.1.0.dist-info/METADATA,sha256=LdrkXjhVhU6W3bq6gr0F9KgRqNkgjBFUXTxL28CWv7g,3586
6
+ gl_py2pyd-0.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
7
+ gl_py2pyd-0.1.0.dist-info/entry_points.txt,sha256=MNnYM2S-NKXNdgYAJ6ZVbXgOpFfZ3F9aQwn8MudjjSU,39
8
+ gl_py2pyd-0.1.0.dist-info/top_level.txt,sha256=uPWv-HYowjiEf_u6fIT0VxVmHRAn9YF2s-ORQePa1U4,14
9
+ gl_py2pyd-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ py2pyd = py2pyd:main
@@ -0,0 +1,2 @@
1
+ module
2
+ py2pyd
module/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ '''
2
+ Author: gu lei
3
+ Date: 2023-01-20 13:52:06
4
+ LastEditTime: 2023-09-26 10:43:03
5
+ LastEditors: gu lei
6
+ '''
7
+ from .single_py2pyd import py2pyd
8
+ from .fileConversion import FileConversion
9
+
10
+ __all__ = ['py2pyd', 'FileConversion']
@@ -0,0 +1,49 @@
1
+ '''
2
+ Author: gu lei
3
+ Date: 2023-01-20 13:52:06
4
+ LastEditTime: 2023-09-26 10:43:03
5
+ LastEditors: gu lei
6
+ '''
7
+ import os
8
+ import time
9
+ from loguru import logger
10
+ from .single_py2pyd import py2pyd
11
+
12
+ class FileConversion:
13
+
14
+ def __init__(self) -> None:
15
+ self.initpy = None
16
+ self.success_count = 0
17
+ self.fail_count = 0
18
+
19
+ def get_all_file(self, path, need_remove: bool): # 遍历此目录下的所有py文件,包含子目录里的py
20
+ for root, dirs, files in os.walk(path):
21
+ if "__init__.py" in files:
22
+ self.process_init_py(root)
23
+ files.remove("__init__.py")
24
+ for name in files:
25
+ if name.endswith(".py"):
26
+ file_path = os.path.join(root, name)
27
+ success = py2pyd(file_path)
28
+ if success:
29
+ self.success_count += 1
30
+ if need_remove:
31
+ os.remove(file_path)
32
+ else:
33
+ self.fail_count += 1
34
+ if self.initpy:
35
+ self.process_init_py(root)
36
+
37
+ logger.info(f"处理完成!成功: {self.success_count} 个文件,失败: {self.fail_count} 个文件")
38
+ return self.success_count > 0 and self.fail_count == 0
39
+
40
+ def process_init_py(self, path):
41
+ init_py_path = os.path.join(path, '__init__.py')
42
+ if self.initpy:
43
+ with open(init_py_path, 'w', encoding='utf-8') as file:
44
+ file.write(self.initpy)
45
+ self.initpy = None
46
+ else:
47
+ with open(init_py_path, 'r', encoding='utf-8') as file:
48
+ self.initpy = file.read()
49
+ os.remove(init_py_path)
@@ -0,0 +1,161 @@
1
+ '''
2
+ Author: gu lei
3
+ Date: 2023-01-20 13:52:06
4
+ LastEditTime: 2023-09-26 10:43:03
5
+ LastEditors: gu lei
6
+ '''
7
+ import os
8
+ import shutil
9
+ import glob
10
+ import subprocess
11
+ import sys
12
+ from loguru import logger
13
+
14
+ def py2pyd(path):
15
+ """
16
+ 将单个Python文件转换为pyd/so文件
17
+
18
+ Args:
19
+ path: Python文件路径
20
+ """
21
+ logger.info(f"==========================================")
22
+ logger.info(f"开始处理文件: {path}")
23
+ logger.info(f"当前工作目录: {os.getcwd()}")
24
+
25
+ # 保存原始工作目录
26
+ original_dir = os.getcwd()
27
+ try:
28
+ # 检查Cython是否已安装
29
+ try:
30
+ import Cython
31
+ except ImportError:
32
+ logger.error("缺少必要的依赖: Cython")
33
+ logger.info("请先安装依赖: pip install cython")
34
+ return False
35
+
36
+ # 转换为绝对路径
37
+ abs_path = os.path.abspath(path)
38
+ folder_path = os.path.dirname(abs_path) # 文件夹路径
39
+ file_path: str = os.path.basename(abs_path) # 带py的文件名
40
+ filename = file_path.split('.py')[0] # 不带py的文件名
41
+
42
+ logger.info(f"目标文件夹路径: {folder_path}")
43
+ logger.info(f"文件名: {file_path}")
44
+
45
+ # 更改工作目录
46
+ os.chdir(folder_path)
47
+ logger.info(f"切换后的工作目录: {os.getcwd()}")
48
+
49
+ # 根据系统配置编译参数和链接参数
50
+ extra_compile_args = []
51
+ extra_link_args = []
52
+
53
+ if sys.platform == 'darwin': # macOS
54
+ extra_compile_args = ['-Wno-unreachable-code']
55
+ # 对于Conda环境,修复重复rpath警告
56
+ if 'conda' in sys.prefix or 'miniconda' in sys.prefix:
57
+ extra_link_args = ['-Wl,-rpath,@loader_path/../lib']
58
+ elif sys.platform.startswith('linux'): # Linux
59
+ extra_compile_args = ['-Wno-unreachable-code']
60
+ # 对于Conda环境,修复重复rpath警告
61
+ if 'conda' in sys.prefix or 'miniconda' in sys.prefix:
62
+ extra_link_args = ['-Wl,-rpath,$ORIGIN/../lib']
63
+ # Windows不需要这些参数
64
+
65
+ # 生成setup.py文件
66
+ with open('setup.py', 'w') as f:
67
+ f.write('from setuptools import setup, Extension\n')
68
+ f.write('from Cython.Build import cythonize\n')
69
+ f.write('import sys\n\n')
70
+
71
+ # 写入编译选项
72
+ f.write('# 配置编译参数\n')
73
+ f.write('extra_compile_args = %s\n' % extra_compile_args)
74
+ f.write('extra_link_args = %s\n\n' % extra_link_args)
75
+
76
+ f.write('# 创建扩展模块\n')
77
+ f.write('extensions = [\n')
78
+ f.write(' Extension(\n')
79
+ f.write(f" '{filename}',\n")
80
+ f.write(f" ['{file_path}'],\n")
81
+ f.write(' extra_compile_args=extra_compile_args,\n')
82
+ f.write(' extra_link_args=extra_link_args\n')
83
+ f.write(' )\n')
84
+ f.write(']\n\n')
85
+
86
+ f.write('setup(\n')
87
+ f.write(f" name='{filename}',\n")
88
+ f.write(' ext_modules=cythonize(\n')
89
+ f.write(' extensions,\n')
90
+ f.write(" compiler_directives={'language_level': '3'}\n")
91
+ f.write(' )\n')
92
+ f.write(')\n')
93
+
94
+ logger.info(f"开始执行 setup.py")
95
+ try:
96
+ subprocess.check_call(['python', 'setup.py', 'build_ext', '--inplace'])
97
+ except subprocess.CalledProcessError as e:
98
+ logger.error(f"执行setup.py失败: {e}")
99
+ logger.error("请确保已正确安装Cython,并且Python环境中有适当的编译器")
100
+ # 清理临时文件
101
+ if os.path.exists('setup.py'):
102
+ os.remove('setup.py')
103
+ return False
104
+
105
+ # 确定扩展名 (.so 或 .pyd)
106
+ is_windows = sys.platform.startswith('win')
107
+ extension = '.pyd' if is_windows else '.so'
108
+ output_file = f"{filename}{extension}"
109
+ logger.info(f"目标文件名: {output_file}")
110
+
111
+ if os.path.exists(output_file):
112
+ logger.info(f"删除已存在的文件: {output_file}")
113
+ os.remove(output_file) # 删除老的文件
114
+
115
+ # 查找生成的扩展文件
116
+ if is_windows:
117
+ ext_files = glob.glob(filename + "*.pyd")
118
+ else:
119
+ ext_files = glob.glob(filename + "*.so")
120
+
121
+ if not ext_files:
122
+ logger.error(f"未找到编译后的文件")
123
+ return False
124
+
125
+ logger.info(f"找到的文件: {ext_files}")
126
+
127
+ os.rename(ext_files[0], output_file) # 改名字,删除多余的cp38-win_amd64.等
128
+ logger.info(f"重命名文件完成")
129
+
130
+ # 清理临时文件
131
+ logger.info("开始清理临时文件")
132
+ if os.path.exists('%s.c' % filename):
133
+ os.remove('%s.c' % filename) # 删除临时文件
134
+ if os.path.exists('build'):
135
+ shutil.rmtree('build')
136
+ if os.path.exists('setup.py'):
137
+ os.remove('setup.py') # 删除掉生成的setup.py
138
+ if os.path.exists('__pycache__'):
139
+ shutil.rmtree('__pycache__') # 删除 __pycache__文件夹
140
+ [os.remove(i) for i in glob.glob("*.ui")] # 删除ui文件
141
+ logger.info("清理临时文件完成")
142
+ logger.info(f"==========================================\n")
143
+ return True
144
+
145
+ except Exception as e:
146
+ logger.error(f"处理文件时发生错误: {e}")
147
+ return False
148
+ finally:
149
+ # 恢复原始工作目录
150
+ os.chdir(original_dir)
151
+ logger.info(f"恢复原始工作目录: {original_dir}")
152
+ logger.info(f"==========================================\n")
153
+
154
+ if __name__ == "__main__":
155
+ import argparse
156
+
157
+ parser = argparse.ArgumentParser(description="将单个Python文件转换为pyd/so文件")
158
+ parser.add_argument("file_path", help="要转换的Python文件路径")
159
+
160
+ args = parser.parse_args()
161
+ py2pyd(args.file_path)
py2pyd.py ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ '''
4
+ Author: gu lei
5
+ Date: 2023-01-20 13:52:06
6
+ LastEditTime: 2023-09-26 10:43:03
7
+ LastEditors: gu lei
8
+ '''
9
+ import os
10
+ import sys
11
+ import argparse
12
+ from loguru import logger
13
+
14
+ def check_dependencies():
15
+ """检查必要的依赖是否已安装"""
16
+ try:
17
+ import Cython
18
+ logger.info(f"检测到Cython版本: {Cython.__version__}")
19
+ return True
20
+ except ImportError:
21
+ logger.error("缺少必要的依赖: Cython \t 请先安装依赖: pip install cython")
22
+ return False
23
+
24
+ from module.single_py2pyd import py2pyd
25
+ from module.fileConversion import FileConversion
26
+
27
+ def main():
28
+ # 检查依赖
29
+ if not check_dependencies():
30
+ sys.exit(1)
31
+
32
+ parser = argparse.ArgumentParser(description="将Python文件转换为pyd/so文件")
33
+ parser.add_argument("path", help="要转换的Python文件或目录路径")
34
+ parser.add_argument("-r", "--recursive", action="store_true", help="递归处理目录")
35
+ parser.add_argument("--remove", action="store_true", help="转换后删除原始.py文件")
36
+
37
+ args = parser.parse_args()
38
+
39
+ path = args.path
40
+
41
+ if not os.path.exists(path):
42
+ logger.error(f"路径不存在: {path}")
43
+ sys.exit(1)
44
+
45
+ success = False
46
+
47
+ if os.path.isfile(path):
48
+ # 处理单个文件
49
+ if not path.endswith(".py"):
50
+ logger.error(f"不是Python文件: {path}")
51
+ sys.exit(1)
52
+ logger.info(f"处理单个文件: {path}")
53
+ success = py2pyd(path)
54
+ if success and args.remove:
55
+ logger.info(f"删除原始文件: {path}")
56
+ os.remove(path)
57
+ elif os.path.isdir(path):
58
+ # 处理目录
59
+ if args.recursive:
60
+ logger.info(f"递归处理目录: {path}")
61
+ converter = FileConversion()
62
+ success = converter.get_all_file(path, args.remove)
63
+ else:
64
+ # 仅处理当前目录下的.py文件
65
+ logger.info(f"处理目录: {path}")
66
+ success_count = 0
67
+ fail_count = 0
68
+ for filename in os.listdir(path):
69
+ if filename.endswith(".py"):
70
+ file_path = os.path.join(path, filename)
71
+ file_success = py2pyd(file_path)
72
+ if file_success:
73
+ success_count += 1
74
+ if args.remove:
75
+ logger.info(f"删除原始文件: {file_path}")
76
+ os.remove(file_path)
77
+ else:
78
+ fail_count += 1
79
+
80
+ logger.info(f"处理完成!成功: {success_count} 个文件,失败: {fail_count} 个文件")
81
+ success = success_count > 0 and fail_count == 0
82
+
83
+ if success:
84
+ logger.info("处理完成!全部转换成功!")
85
+ sys.exit(0)
86
+ else:
87
+ logger.error("处理完成,但有部分文件转换失败!")
88
+ sys.exit(1)
89
+
90
+ if __name__ == "__main__":
91
+ main()