python自动使用虚拟环境和安装依赖
代码如下,Windows环境测试通过(1. 判断是否在虚拟环境里;2. 判断当前目录下是否有venv文件夹;3. 如果都没有则通过python -m venv venv来创建;4. 然后调用venv里的pip来安装模块)
1 import platform 2 import re 3 import subprocess 4 import sys 5 from contextlib import AbstractContextManager 6 from pathlib import Path 7 8 9 class EnsureImport(AbstractContextManager): 10 """Auto install modules if import error. 11 12 Usage:: 13 >>> for _ range(EnsureImport.retry): 14 ... with EnsureImport( 15 ... multipart='python-multipart', dotenv='python-dotenv' 16 ... ) as _m: 17 ... import six 18 ... import multipart 19 ... from dotenv import load_dotenv 20 ... # more imports ... 21 ... if _m.ok: 22 ... break 23 ... 24 """ 25 26 mapping = { 27 "multipart": "python-multipart", 28 "dotenv": "python-dotenv", 29 "snap7": "python-snap7", 30 } 31 retry = 30 32 33 def __init__(self, **kwargs): 34 self.exception = None 35 self._success = True 36 self.package_mapping = dict(self.mapping, **kwargs) 37 38 @property 39 def ok(self) -> bool: 40 return self._success 41 42 def __exit__(self, exc_type, exc_value, traceback): 43 if isinstance(exc_value, (ImportError, ModuleNotFoundError)): 44 self.exception = exc_value 45 self._success = False 46 self.run() 47 return True 48 49 def run(self): 50 e = self.exception 51 modules = re.findall(r"'([a-zA-Z][0-9a-zA-Z_-]+)'", str(e)) 52 if "--no-install" in sys.argv or not modules: 53 raise e 54 ms = (self.package_mapping.get(i, i) for i in modules) 55 rc = self.install_and_extend_sys_path(*ms) 56 if rc: 57 sys.exit(rc) 58 59 @staticmethod 60 def is_venv() -> bool: 61 """Whether in a virtual environment(also work for poetry)""" 62 return hasattr(sys, "real_prefix") or ( 63 hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix 64 ) 65 66 @staticmethod 67 def run_and_echo(cmd: str) -> int: 68 print("-->\n", cmd, flush=True) 69 return subprocess.call(cmd, shell=True) 70 71 @staticmethod 72 def log_error(action: str) -> None: 73 print(f"ERROR: failed to {action}") 74 75 @classmethod 76 def install_and_extend_sys_path(cls, *packages) -> int: 77 py = Path(sys.executable) 78 depends = " ".join(packages) 79 if not cls.is_venv(): 80 p = Path.cwd() / "venv" 81 if not p.exists(): 82 if cls.run_and_echo(f"{py} -m venv venv"): 83 cls.log_error(f"create virtual environment for {py}") 84 return 1 85 if platform.platform().lower().startswith("win"): 86 py = p / "Scripts" / "python.exe" 87 else: 88 py = p / "bin/python" 89 cls.run_and_echo(f"{py} -m pip install --upgrade pip") 90 lib = list(p.rglob("site-packages"))[0] 91 sys.path.append(lib.as_posix()) 92 if cls.run_and_echo(f"{py} -m pip install {depends}"): 93 cls.log_error(f"install {depends}") 94 return 2 95 return 0 96 97 98 for _ in range(EnsureImport.retry): 99 with EnsureImport(dotenv='python-dotenv') as _f: 100 from dotenv import load_dotenv 101 if _f.ok: 102 break 103 104 105 load_dotenv()
#######################
以下为旧版代码,可以不用看
#######################
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | import os import platform import re import sys from pathlib import Path def is_venv() - > bool : """判断是否处于虚拟环境(也适用于poetry的)""" if hasattr (sys, "real_prefix" ): return True return hasattr (sys, "base_prefix" ) and sys.base_prefix ! = sys.prefix def run_and_echo(cmd) - > int : print ( "-->\n" , cmd, flush = True ) return os.system(cmd) def install_and_rerun( * packages): py = Path(sys.executable) if not is_venv(): if not (p : = Path( "venv" )).exists(): if run_and_echo(f "{py} -m venv venv" ): return 1 if platform.platform().lower().startswith( "win" ): py = p / "Scripts" / "python.exe" else : py = p / "bin/python" if run_and_echo(f "{py} -m pip install {' '.join(packages)}" ): return 2 cmd = f "{py} {sys.argv[0]} --no-install {' '.join(sys.argv[1:])}" return run_and_echo(cmd) try : import kivy except ImportError as e: if "--no-install" in sys.argv: raise e modules = re.findall(r "'([a-zA-Z_-]+)'" , str (e)) sys.exit(install_and_rerun( * modules)) def main(): pass if __name__ = = '__main__' : main() |
运行时,如果import失败,会判断是否处于虚拟环境,是的话,直接pip install报错的缺失包,然后自动重新执行脚本;
否则,判断当前路径是否有venv文件,有的话使用venv/*/python,否则使用python -m venv venv创建它
优化成class以便PyCharm可以折叠
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | import os import platform import random import re import sys from pathlib import Path class EnsureImport: def __init__( self , e): self .exception = e def run( self ): e = self .exception if "--no-install" in sys.argv: raise e modules = re.findall(r "'([a-zA-Z_-]+)'" , str (e)) sys.exit( self .install_and_rerun( * modules)) @staticmethod def is_venv() - > bool : """判断是否处于虚拟环境(也适用于poetry的)""" if hasattr (sys, "real_prefix" ): return True return hasattr (sys, "base_prefix" ) and sys.base_prefix ! = sys.prefix @staticmethod def run_and_echo(cmd) - > int : print ( "-->\n" , cmd, flush = True ) return os.system(cmd) @classmethod def install_and_rerun( cls , * packages): py = Path(sys.executable) command = " " .join(sys.argv) if not cls .is_venv(): if not (p : = Path( "venv" )).exists(): if cls .run_and_echo(f "{py} -m venv venv" ): return 1 <br> else :<br> cls .run_and_echo(f '{py} -m pip install -U pip' ) if platform.platform().lower().startswith( "win" ): py = p / "Scripts" / "python.exe" else : py = p / "bin/python" return cls .run_and_echo(f "{py} {command}" ) depends = " " .join(packages) if cls .run_and_echo(f "{py} -m pip install {depends}" ): return 2 cmd = f "{py} {command} --no-install" return cls .run_and_echo(cmd) try : import dearpygui.dearpygui as dpg except ImportError as e: EnsureImport(e).run() def main(): print (dpg) if __name__ = = "__main__" : main() |
使用了isort+black+ruff进行代码格式化
进一步优化成with风格:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | import os import platform import re import sys from pathlib import Path class EnsureImport: """Auto install module if import error. Usage:: >>> with EnsureImport(): ... import six """ def __init__( self ): self .exception = None def __enter__( self ): return self def __exit__( self , exc_type, exc_value, traceback): if exc_type is not None : if isinstance (exc_value, (ImportError, ModuleNotFoundError)): self .exception = exc_value self .run() def run( self ): e = self .exception if "--no-install" in sys.argv: raise e modules = re.findall(r "'([a-zA-Z_-]+)'" , str (e)) sys.exit( self .install_and_rerun( * modules)) @staticmethod def is_venv() - > bool : """判断是否处于虚拟环境(也适用于poetry的)""" if hasattr (sys, "real_prefix" ): return True return hasattr (sys, "base_prefix" ) and sys.base_prefix ! = sys.prefix @staticmethod def run_and_echo(cmd) - > int : print ( "-->\n" , cmd, flush = True ) return os.system(cmd) @classmethod def install_and_rerun( cls , * packages): py = Path(sys.executable) command = " " .join(sys.argv) if not cls .is_venv(): if not (p : = Path( "venv" )).exists(): if cls .run_and_echo(f "{py} -m venv venv" ): return 1 if platform.platform().lower().startswith( "win" ): py = p / "Scripts" / "python.exe" else : py = p / "bin/python" return cls .run_and_echo(f "{py} {command}" ) depends = " " .join(packages) if cls .run_and_echo(f "{py} -m pip install {depends}" ): return 2 cmd = f "{py} {command} --no-install" return cls .run_and_echo(cmd) with EnsureImport(): import six def main(): print (six.__file__) if __name__ = = "__main__" : main() |
进一步优化成支持多import且只run一次的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | import platform import re import subprocess import sys from contextlib import AbstractContextManager from pathlib import Path class EnsureImport(AbstractContextManager): """Auto install modules if import error. Usage:: >>> for _ range(EnsureImport.retry): ... with EnsureImport( ... multipart='python-multipart', dotenv='python-dotenv' ... ) as _m: ... import six ... import multipart ... from dotenv import load_dotenv ... # more imports ... ... if _m.ok: ... break ... """ retry = 10 def __init__( self , * * kwargs): self .exception = None self ._success = True self .package_mapping = kwargs @property def ok( self ) - > bool : return self ._success def __exit__( self , exc_type, exc_value, traceback): if isinstance (exc_value, (ImportError, ModuleNotFoundError)): self .exception = exc_value self ._success = False self .run() return True def run( self ): e = self .exception if "--no-install" in sys.argv: raise e modules = re.findall(r "'([a-zA-Z_-]+)'" , str (e)) if mp : = self .package_mapping: modules = [mp.get(i, i) for i in modules] if rc : = self .install_and_extend_sys_path( * modules): sys.exit(rc) @staticmethod def is_venv() - > bool : """判断是否处于虚拟环境(也适用于poetry的)""" if hasattr (sys, "real_prefix" ): return True return hasattr (sys, "base_prefix" ) and sys.base_prefix ! = sys.prefix @staticmethod def run_and_echo(cmd: str ) - > int : print ( "-->\n" , cmd, flush = True ) return subprocess.call(cmd, shell = True ) @staticmethod def log_error(action: str ) - > None : print (f "ERROR: failed to {action}" ) @classmethod def install_and_extend_sys_path( cls , * packages) - > int : py = Path(sys.executable) depends = " " .join(packages) if not cls .is_venv(): if not (p : = Path( "venv" )).exists():<br> if depends.lower() = = 'dearpygui' and sys.version_info > = ( 3 , 11 ):<br> py = Path( 'python3.10' ) if cls .run_and_echo(f "{py} -m venv venv" ): cls .log_error(f "create virtual environment for {py}" ) return 1 if platform.platform().lower().startswith( "win" ): py = p / "Scripts" / "python.exe" else : py = p / "bin/python" lib = list (p.rglob( "site-packages" ))[ 0 ] sys.path.append(lib.as_posix()) if cls .run_and_echo(f "{py} -m pip install {depends}" ): cls .log_error(f "install {depends}" ) return 2 return 0 for _ in range (EnsureImport.retry): with EnsureImport(multipart = "python-multipart" , dotenv = "python-dotenv" ) as _m: import multipart import six from dotenv import load_dotenv if _m.ok: break def main(): print (six.__file__) print (multipart.__file__) load_dotenv() if __name__ = = "__main__" : main() |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现