如何修改继承build_py的自定义构建类以在临时目录构建并解决报错?
问题描述
我有一个setup.py文件,其中包含继承自build_py的自定义构建类,为适配旧环境,优先从distutils导入,失败则用setuptools:
try: warnings.filterwarnings('ignore', message='.*distutils package is deprecated.*', category=DeprecationWarning) from distutils.core import setup from distutils.command.build_py import build_py except: from setuptools import setup from setuptools.command.build_py import build_py class MyBuild(build_py): #... 现有自定义代码... #... 新增自定义代码... def run(self): build_py.run(self) if __name__ == '__main__': setup( name="...", version="...", #... cmdclass={'build_py': MyBuild} )
在VM客户端执行pip install .时失败(客户端外执行正常),原因是pip尝试在源目录创建构建目录,触发权限问题。环境为Windows系统,Python 3.9/3.10/3.11,pip 22.3.1/23.3版本均存在该问题;使用旧版pip会出现ImportError: cannot import name 'Mapping' from 'collections'错误。
我尝试通过tempfile.gettempdir()获取临时目录作为构建目录,在自定义类中添加了以下代码:
def initialize_options(self): self.build_base = None self.build_lib = None print('Set self.build_base and self.build_lib to None') def finalize_options(self): if not self.build_base: self.build_base = tempfile.gettempdir() print('Set self.build_base to ', self.build_base) if not self.build_lib: self.build_lib = os.path.join(self.build_base, 'lib') print('Set self.build_lib to ', self.build_lib)
但执行pip install .时抛出异常:
Installing build dependencies ... done Getting requirements to build wheel ... error error: subprocess-exited-with-error × Getting requirements to build wheel did not run successfully. │ exit code: 1 ╰─> [55 lines of output] Set self.build_base and self.build_lib to None Set self.build_base to <...>\AppData\Local\Temp Set self.build_lib to <...>\AppData\Local\Temp\lib Traceback (most recent call last): File "C:\pyvenv\<...>\Lib\site-packages\pip\_vendor\pep517\in_process\_in_process.py", line 351, in <module> main() File "C:\pyvenv\<...>\Lib\site-packages\pip\_vendor\pep517\in_process\_in_process.py", line 333, in main json_out['return_val'] = hook(**hook_input['kwargs']) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\pyvenv\<...>\Lib\site-packages\pip\_vendor\pep517\in_process\_in_process.py", line 118, in get_requires_for_build_wheel return hook(config_settings) ^^^^^^^^^^^^^^^^^^^^^ File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\build_meta.py", line 355, in get_requires_for_build_wheel return self._get_build_requires(config_settings, requirements=['wheel']) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\build_meta.py", line 325, in _get_build_requires self.run_setup() File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\build_meta.py", line 341, in run_setup exec(code, locals()) File "<string>", line 138, in <module> File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\_distutils\core.py", line 185, in setup return run_commands(dist) ^^^^^^^^^^^^^^^^^^ File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\_distutils\core.py", line 201, in run_commands dist.run_commands() File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\_distutils\dist.py", line 969, in run_commands self.run_command(cmd) File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\dist.py", line 989, in run_command super().run_command(command) File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\_distutils\dist.py", line 988, in run_command cmd_obj.run() File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\command\egg_info.py", line 318, in run self.find_sources() File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\command\egg_info.py", line 326, in find_sources mm.run() File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\command\egg_info.py", line 548, in run self.add_defaults() File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\command\egg_info.py", line 586, in add_defaults sdist.add_defaults(self) File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\command\sdist.py", line 113, in add_defaults super().add_defaults() File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\_distutils\command\sdist.py", line 249, in add_defaults self._add_defaults_python() File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\command\sdist.py", line 125, in _add_defaults_python self.filelist.extend(build_py.get_source_files()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\_distutils\command\build_py.py", line 303, in get_source_files return [module[-1] for module in self.find_all_modules()] ^^^^^^^^^^^^^^^^^^^^^^^ File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\_distutils\command\build_py.py", line 293, in find_all_modules if self.py_modules: ^^^^^^^^^^^^^^^ File "C:\<...>\AppData\Local\Temp\pip-build-env-42o2vdhl\overlay\Lib\site-packages\setuptools\_distutils\cmd.py", line 107, in __getattr__ raise AttributeError(attr) AttributeError: py_modules. Did you mean: 'find_modules'? [end of output]
尝试在finalize_options末尾添加如下代码时,又陷入递归循环:
# self.set_undefined_options('build_py', ('build_base', 'build_base'), # ('build_lib', 'build_lib'))
求正确的实现方式。
解决方案
问题出在重写initialize_options和finalize_options时,没有调用父类的对应方法,导致父类中负责初始化的属性(比如py_modules)未被设置,进而触发AttributeError。正确做法是先调用父类方法完成基础初始化,再覆盖自定义配置。
另外需要提前导入tempfile和os模块,避免导入错误。修改后的完整代码如下:
import os import tempfile import warnings try: warnings.filterwarnings('ignore', message='.*distutils package is deprecated.*', category=DeprecationWarning) from distutils.core import setup from distutils.command.build_py import build_py except: from setuptools import setup from setuptools.command.build_py import build_py class MyBuild(build_py): #... 现有自定义代码... def initialize_options(self): # 先调用父类初始化方法,确保所有默认属性被设置 super().initialize_options() # 重置build_base为None,后续在finalize中自定义 self.build_base = None def finalize_options(self): # 如果build_base未设置,使用系统临时目录 if not self.build_base: self.build_base = tempfile.gettempdir() # 调用父类finalize方法,让父类基于build_base处理其他依赖属性 super().finalize_options() # 若需要自定义build_lib,可在此覆盖父类默认值 # self.build_lib = os.path.join(self.build_base, 'custom_lib') def run(self): build_py.run(self) if __name__ == '__main__': setup( name="...", version="...", #... cmdclass={'build_py': MyBuild} )
关键说明
- 必须调用父类方法:
initialize_options和finalize_options中先执行父类方法,确保py_modules、package_dir等必要属性完成初始化,避免属性缺失错误。 - 避免递归循环:不要使用
set_undefined_options('build_py', ...),当前类是build_py的子类,该操作会触发递归调用自身。 - 临时目录的使用:将
build_base设为系统临时目录后,父类的finalize_options会自动生成build_lib的默认路径(通常为build_base/lib),无需手动设置,若有特殊需求再覆盖即可。
这样修改后,pip会使用系统临时目录作为构建目录,避开源目录的权限问题,同时保证所有构建所需属性都被正确初始化。
内容的提问来源于stack exchange,提问作者Alan
相关产品推荐
相关产品推荐

