
維護(hù)多套Python版本的Linux服務(wù)器最讓人頭疼的不是虛擬環(huán)境切來切去而是系統(tǒng)級依賴在各版本之間打架。這段時間我就被libselinux這個包折騰了一下午機(jī)器上同時跑著CentOS自帶的Python 2.7、源碼編譯的Python 3.8還有用pyenv裝的Python 3.12結(jié)果在Python 3.12里只要import selinux直接給你一個ModuleNotFoundError。而RPM層面rpm -qa | grep selinux清清楚楚地列著libselinux-python包是裝了可Python 3.12就是找不到這個模塊。這事的本質(zhì)就是同名RPM包的排他機(jī)制和多Python版本ABI不兼容疊加在一起產(chǎn)生的包存在但環(huán)境不可用的尷尬局面。這篇文章就把我這次排查和解決的全過程拆開講清楚適合正在維護(hù)多版本Python環(huán)境、或者在舊系統(tǒng)上裝新Python后遇到selinux相關(guān)報(bào)錯的運(yùn)維和開發(fā)者參考。1. 問題全景RPM同名包沖突與多Python版本糾纏1.1 一個典型的包存在導(dǎo)入失敗場景先還原一下現(xiàn)場。我的測試服務(wù)器是CentOS 7系列系統(tǒng)默認(rèn)Python版本是2.7.5因?yàn)橐芤恍├夏_本這個版本不能動。后來項(xiàng)目需要我用源碼方式編譯安裝了Python 3.8.10再后來又用pyenv裝了Python 3.12.1做新項(xiàng)目的隔離環(huán)境。其實(shí)pyenv管理的Python還算規(guī)矩pyenv install出來的解釋器會有獨(dú)立的site-packages目錄不太容易和系統(tǒng)環(huán)境串臺。問題是Python 3.8和后續(xù)的3.12在某些依賴上需要用到SELinux的Python綁定——準(zhǔn)確說是selinux這個Python模塊它是libselinux官方提供的C擴(kuò)展綁定用于在Python層面執(zhí)行SELinux相關(guān)的查詢和策略操作。當(dāng)時我在Python 3.8里執(zhí)行python3 -c import selinux一切正常。但切到Python 3.12pyenv環(huán)境再跑同樣的命令直接報(bào)ModuleNotFoundError: No module named selinux然后我去查系統(tǒng)RPM包rpm -qa | grep -E selinux|python.*selinux輸出里有l(wèi)ibselinux-python-2.5-15.el7.x86_64也有l(wèi)ibselinux-python3-2.5-14.el7.x86_64。看到這里你可能會問既然libselinux-python3都裝了Python 3.12怎么會找不到問題就在這兒這個RPM包里面的python3指的是系統(tǒng)自帶的Python 3.6而不是我后來裝的Python 3.8/3.12。RPM包管理器安裝Python模塊時會按照編譯時的路徑寫死比如/usr/lib/python3.6/site-packages/。它根本不知道、也管不到你用源碼方式裝到/usr/local/python3.8或者pyenv隔離環(huán)境里的Python。1.2 libselinux是什么為什么會綁定Python聊解決方案之前得先把libselinux這個包的身份搞清楚。libselinux是SELinuxSecurity-Enhanced Linux用戶態(tài)的核心庫提供了一組C函數(shù)接口用來做安全上下文查詢、策略加載、文件標(biāo)簽管理等操作。幾乎所有主流Linux發(fā)行版都默認(rèn)啟用SELinux或者至少編譯了它的基礎(chǔ)庫所以libselinux.so.1這個動態(tài)庫是系統(tǒng)底層的常駐人口。但光有C庫還不夠上層應(yīng)用如果用Python寫就需要一個從Python到C語言的橋接層。于是libselinux項(xiàng)目自己維護(hù)了一套Python綁定也就是selinux這個模塊。它本質(zhì)上是一個.so擴(kuò)展文件比如selinux/_selinux.cpython-38-x86_64-linux-gnu.so由Cython或CPython C API編譯生成負(fù)責(zé)把Python的調(diào)用翻譯成libselinux.so.1提供的C函數(shù)調(diào)用。這里就有一個關(guān)鍵點(diǎn)Python的C擴(kuò)展模塊是綁定特定Python主版本號的。Python 2和Python 3的ABI完全不一樣即使在Python 3系列內(nèi)部CPython 3.8和3.12的C API也有差異雖然大部分情況下兼容但不同小版本編譯出的.so文件不能想當(dāng)然地互相替代。理論上3.8編出來的擴(kuò)展在3.12里大概率可以加載除非用了一些deprecated API但你不可能指望Python 2時代編譯的selinux.so被Python 3.12加載那是絕對不可能的。1.3 為什么RPM層面的同名不等于Python層面的可用RPM包管理器有一個鐵律同一個包名同一時間系統(tǒng)里只能存在一個版本。libselinux-python這個包給Python 2用libselinux-python3給Python 3用它們在RPM倉庫里是兩個不同的包名一個帶python一個帶python3所以理論上可以共存。但繼續(xù)往下挖就會發(fā)現(xiàn)問題libselinux-python3-2.5-14.el7.x86_64這個RPM包在CentOS 7上編譯時依賴的是/usr/bin/python3也就是系統(tǒng)自帶的Python 3.6解釋器。RPM安裝的時候它會把文件放到Python 3.6的默認(rèn)site-packages路徑/usr/lib/python3.6/site-packages/selinux/你后來用源碼編譯的Python 3.8默認(rèn)prefix是/usr/local對應(yīng)的site-packages是/usr/local/lib/python3.8/site-packages/Python 3.12如果是pyenv管理的路徑更是完全隔離~/.pyenv/versions/3.12.1/lib/python3.12/site-packages/這三個路徑在Python解釋器看來是三個完全不同的世界。RPM只保證它自己知道的那個解釋器能找到模塊至于你自己裝的其他Python版本它一概不管。這就是包存在但環(huán)境不可用的根本原因包管理器維護(hù)的是系統(tǒng)全局的文件系統(tǒng)路徑而Python解釋器維護(hù)的是它自己的sys.path搜索路徑兩者在多自編譯Python版本共存的場景下產(chǎn)生了嚴(yán)重脫節(jié)。2. 沖突根源拆解RPM的Excel式排他規(guī)則2.1 RPM包管理器的基本游戲規(guī)則要把這個問題講透還是得回到RPM本身的工作原理。RPMRed Hat Package Manager本質(zhì)是一個文件清單腳本元信息的打包格式。安裝一個RPM包時管理器會檢查幾個東西依賴關(guān)系這個包依賴哪些包系統(tǒng)里是否滿足版本要求。文件沖突要安裝的文件是否已經(jīng)被其他包占用。同名包版本沖突是否已經(jīng)安裝了相同名稱的其他版本。這里第三點(diǎn)是讓很多人踩坑的地方。假設(shè)你已經(jīng)安裝了libselinux-python-2.5-15.el7再去執(zhí)行yum install libselinux-python-2.5-16.el7如果兩個包的名稱相同但版本不同yum會默認(rèn)做升級而不是共存。如果想要同時保留兩個版本必須顯式指定--install或者用yum install配合--setoptobsoletes0之類的參數(shù)而且即便強(qiáng)制安裝了RPM數(shù)據(jù)庫中的文件清單也會混亂以后卸載、升級都容易出問題。在libselinux的場景里libselinux-python和libselinux-python3雖然名稱不完全相同但它們都包含了一個關(guān)鍵路徑下的文件比如/usr/lib/python2.7/site-packages/selinux/ /usr/lib/python3.6/site-packages/selinux/包名不同路徑不同所以RPM層面并不沖突。真正沖突的地方在于不同Python版本的site-packages路徑天然隔離導(dǎo)致看起來都有、實(shí)際各管各。很多人錯誤地以為裝了libselinux-python3所有Python 3都能用實(shí)際上只有編譯該包時頭文件指向的那個解釋器能用。2.2 看清RPM包與Python模塊的映射關(guān)系為了讓你更直觀地理解我做了一張對照表列出同名類似物但實(shí)際差異巨大的關(guān)鍵信息項(xiàng)libselinux-pythonlibselinux-python3源碼/自編譯Python目標(biāo)解釋器Python 2.7Python 3.6系統(tǒng)自帶Python 3.8 / 3.12自定義安裝路徑/usr/lib/python2.7/site-packages/usr/lib/python3.6/site-packages/usr/local/lib/python3.8/site-packages或 pyenv路徑模塊文件selinux/_selinux.soPython 2 ABIselinux/_selinux.cpython-36m-x86_64-linux-gnu.so需要為3.8/3.12獨(dú)立編譯RPM識別方式能識別系統(tǒng)和yum能識別系統(tǒng)yum環(huán)境RPM完全不識別Python能否導(dǎo)入只有Python 2能只有系統(tǒng)Python 3.6能只有自己編譯的Python 3.8/3.12能從這個表可以看出來同一堆代碼libselinux的Python綁定因?yàn)槟繕?biāo)解釋器不同在文件系統(tǒng)里就產(chǎn)生了好幾個互不相通的副本。RPM管得了系統(tǒng)路徑下的那幾份管不了你個人編譯安裝的Python解釋器。2.3 常見錯誤想法rpm -qa里有包Python就能用很多人在排查時覺得不可思議我明明rpm -qa | grep python能看到一大堆包為什么Python 3.12還是import失敗原因就一句話RPM數(shù)據(jù)庫是系統(tǒng)級的Python的包搜索路徑是解釋器級的。你在RPM數(shù)據(jù)庫里查到某個包只代表這個包向文件系統(tǒng)里釋放了一些文件并不代表所有Python解釋器都會自動加載它們。Python解釋器啟動后會按順序搜索sys.path這個列表里包含當(dāng)前腳本所在目錄PYTHONPATH環(huán)境變量指定的目錄解釋器編譯時的prefix對應(yīng)的標(biāo)準(zhǔn)庫和site-packages目錄.pth文件里追加的路徑它不會去/usr/lib/python2.7/site-packages里找Python 3能用的模塊除非你手動把那個路徑塞進(jìn)sys.path。而且就算塞進(jìn)去了Python 2 ABI編譯的.so文件在Python 3里加載時大概率直接ImportError: dynamic module does not define module export function或者更隱晦的undefined symbol錯誤。所以正確的心智模型是RPM只是文件分發(fā)工具Python模塊的可用性完全取決于解釋器能不能搜到正確ABI的擴(kuò)展文件。3. 多Python版本兼容的方案設(shè)計(jì)與選型3.1 先盤一下幾條路線解決這個問題的思路有很多但質(zhì)量參差不齊。我結(jié)合這幾年的運(yùn)維經(jīng)驗(yàn)把主流方案分成四類先列個對比表方案原理優(yōu)點(diǎn)缺點(diǎn)推薦度方案Apip安裝selinux從PyPI安裝純Python封裝或綁定簡單快速新版PyPI上的selinux包可能不維護(hù)或需要系統(tǒng)libselinux頭文件分情況方案B從RPM包中提取.so文件解包RPM手動把selinux目錄復(fù)制到目標(biāo)Python的site-packages不需要編譯ABI可能不匹配Python 2與3的文件基本不可互用低方案C源碼編譯selinux綁定下載libselinux源碼用目標(biāo)Python運(yùn)行setup.py完全匹配目標(biāo)解釋器ABI干凈可靠需要編譯環(huán)境和依賴頭文件高方案D軟鏈接/改名把Python 3.6的.so硬指向Python 3.12零成本大概率崩潰或損壞site-packages極其不推薦極低3.2 為什么新版pip install selinux不一定靠譜首選可能有人會想PyPI上不是有selinux這個包嗎pip install selinux不行嗎我在Python 3.12環(huán)境里試過確實(shí)能裝上但這個包和系統(tǒng)libselinux的版本匹配度是個大問題。PyPI上的selinux包本質(zhì)是同一個上游項(xiàng)目SELinuxProject/selinux的Python綁定打包發(fā)布但它依賴系統(tǒng)的libselinux.so.1動態(tài)庫如果動態(tài)庫版本太老或者頭文件缺失安裝時編譯就會失敗。而且這個包的發(fā)布節(jié)奏不一定跟得上系統(tǒng)的安全補(bǔ)丁在CentOS 7這種老系統(tǒng)上系統(tǒng)自帶的libselinux庫版本可能很老2.5而PyPI上最新版selinux可能期望更高版本的庫裝完照樣報(bào)undefined symbol。結(jié)論是如果你的系統(tǒng)libselinux庫版本比較老還是優(yōu)先考慮和系統(tǒng)同源的libselinux源碼版本而不是追新的PyPI包。3.3 徹底搞懂為什么源碼編譯是唯一穩(wěn)的路線真正穩(wěn)妥的做法是讓libselinux官方的Python綁定代碼針對每一個需要兼容的解釋器重新編譯一次。這個過程本質(zhì)上就是拿到與系統(tǒng)libselinux版本匹配的源碼。在目標(biāo)Python解釋器環(huán)境下執(zhí)行setup.py build和setup.py install。編譯成功后site-packages里出現(xiàn)該解釋器專屬的selinux目錄內(nèi)含ABI匹配的.so文件。這樣做的好處是ABI絕對匹配編譯時用的就是目標(biāo)解釋器的頭文件和鏈接參數(shù)不擔(dān)心跨版本不兼容。版本可控可以指定和系統(tǒng)libselinux同版本的源碼避免版本漂移。路徑干凈每個Python環(huán)境各有一份獨(dú)立的site-packages副本互不干擾RPM也管不著完全符合多版本共存的需求??蓮?fù)現(xiàn)相同流程可以隨時為新的Python版本比如3.13重新編譯一遍秒級擴(kuò)展。3.4 各方案適用的場景邊界不過這四類方案不是說一定要死磕源碼編譯。實(shí)踐中我一般這樣選如果只是臨時跑一個腳本缺selinux就報(bào)錯可以試著用pip install selinux能過就用不糾結(jié)性能。但要注意這個包裝完后要確認(rèn)能不能正常import并且執(zhí)行基礎(chǔ)調(diào)用最好跑一下selinux.selinux_getenforcemode()。如果系統(tǒng)是CentOS 7/RHEL 7這一類老發(fā)行版系統(tǒng)自帶Python是2.7你新裝的Python版本又比較新直接源碼編譯綁定是唯一靠譜的路徑因?yàn)槔舷到y(tǒng)的repo里沒有匹配新解釋器的RPM包。如果只是Python 3.6自帶的系統(tǒng)環(huán)境那根本不用折騰確認(rèn)yum install libselinux-python3已經(jīng)裝了就行。4. 實(shí)操全過程為Python 3.12編譯libselinux綁定4.1 環(huán)境準(zhǔn)備與依賴清點(diǎn)先說環(huán)境我這次操作的系統(tǒng)是CentOS 7.9內(nèi)核版本3.10SELinux處于Enforcing模式。系統(tǒng)的libselinux庫版本是2.5。要編譯Python綁定需要準(zhǔn)備以下幾樣?xùn)|西# 安裝編譯工具鏈 yum install -y gcc make python3-devel # 安裝libselinux開發(fā)頭文件編譯Python擴(kuò)展時需要 yum install -y libselinux-devel # 確認(rèn)目標(biāo)Python解釋器 pyenv versions # 輸出中應(yīng)有 3.12.1 # 查看當(dāng)前系統(tǒng)SELinux庫版本 rpm -qa | grep libselinux # libselinux-2.5-15.el7.x86_64有一點(diǎn)要特別注意編譯Python擴(kuò)展時python3-devel和libselinux-devel的版本必須與運(yùn)行時一致。CentOS 7上默認(rèn)的python3是3.6版本如果你pip install selinux或源碼編譯時誤用了系統(tǒng)Python 3.6的python3-config編譯出來的東西只會丟到3.6的site-packages里對3.12沒有任何幫助。所以執(zhí)行編譯安裝步驟時必須明確指定用哪個解釋器。4.2 獲取與系統(tǒng)匹配的libselinux源碼這里有個技巧不要隨便去GitHub上拉最新的master分支應(yīng)該去找和系統(tǒng)libselinux版本對應(yīng)的tag。比如系統(tǒng)是2.5那就可以去找2.5或相近的release tag。# 克隆官方倉庫 git clone https://github.com/SELinuxProject/selinux.git cd selinux # 切換到對應(yīng)版本分支 git checkout 2.5 # 進(jìn)入libselinux子項(xiàng)目 cd libselinux在libselinux的源碼目錄里Python綁定相關(guān)的文件通常位于src/下的selinux.py、selinux_python.i或selinux/子目錄不同版本結(jié)構(gòu)略有差異。2.5版本的目錄大概是src/selinux/、src/selinux.c和src/setup.py。如果不確定路徑可以這樣快速找到find . -name setup.py | grep -i python找到setup.py后看一下里面的配置。通常這個setup.py會調(diào)用distutils.core.setup把selinux.c編譯成_selinux.so擴(kuò)展并打包selinux這個頂層Python包里面包含__init__.py和編譯出來的_selinux擴(kuò)展。4.3 用目標(biāo)Python解釋器執(zhí)行編譯安裝關(guān)鍵步驟來了。要確保是用Python 3.12去執(zhí)行setup.py而不是系統(tǒng)默認(rèn)的python3也就是3.6。給我的pyenv里的Python 3.12.1指定完整路徑~/.pyenv/versions/3.12.1/bin/python setup.py build ~/.pyenv/versions/3.12.1/bin/python setup.py install如果編譯過程中報(bào)錯缺少頭文件通常是因?yàn)閘ibselinux-devel沒裝全或者沒有把/usr/include/selinux路徑暴露給編譯器。這時可以通過環(huán)境變量指定export CFLAGS-I/usr/include export LDFLAGS-L/usr/lib64 -lselinux ~/.pyenv/versions/3.12.1/bin/python setup.py build編譯成功后會看到類似信息running build running build_ext building _selinux extension gcc -pthread ...然后install。install后驗(yàn)證一下是否真的裝到了Python 3.12的site-packages路徑~/.pyenv/versions/3.12.1/bin/python -c import selinux; print(selinux.__file__)正常會輸出~/.pyenv/versions/3.12.1/lib/python3.12/site-packages/selinux/__init__.py說明綁定已經(jīng)進(jìn)入3.12的環(huán)境并且_selinux.so是和3.12 ABI匹配的。4.4 多版本共存同一臺機(jī)器的并行處理如果你也想讓Python 3.8同時能用只需要把上面的~/.pyenv/versions/3.12.1/bin/python換成3.8的路徑再執(zhí)行一遍setup.py build和setup.py install。因?yàn)閟ite-packages路徑是按解釋器隔離的兩份編譯結(jié)果各自存放在不同目錄完全不會互相覆蓋。不過要注意一個細(xì)節(jié)編譯Python 3.8綁定之前最好清一下build臨時目錄。因?yàn)閟etup.py build會把中間產(chǎn)物緩存在源碼目錄的build/下如果上一次是用3.12編譯的這次直接用3.8編譯有些舊的對象文件可能會導(dǎo)致沖突或鏈接到錯誤的Python庫。最穩(wěn)妥的做法是rm -rf build ~/.pyenv/versions/3.8.10/bin/python setup.py build ~/.pyenv/versions/3.8.10/bin/python setup.py install4.5 驗(yàn)證selinux功能是否真的可用編譯安裝之后只驗(yàn)證import不報(bào)錯還不夠還得驗(yàn)證功能是否正常。我一般會跑一條最基礎(chǔ)的SELinux API~/.pyenv/versions/3.12.1/bin/python -c import selinux print(SELinux enabled:, selinux.is_selinux_enabled()) print(Enforce mode:, selinux.selinux_getenforcemode()) 如果輸出SELinux enabled: 1 Enforce mode: (0, 1)那就說明從Python綁定到C庫的整個鏈路是通的。順便說一句selinux_getenforcemode()返回的是一個元組第一個元素是狀態(tài)碼第二個是模式值0Permissive1Enforcing。5. 踩坑實(shí)錄常見問題與排查技巧5.1 ModuleNotFoundErrorNo module named selinux這個報(bào)錯出現(xiàn)頻率最高。排查思路分三步確認(rèn)目標(biāo)解釋器的site-packages里有沒有selinux目錄python3.12 -c import sys; print(\n.join(sys.path))然后在列出的路徑中找selinux目錄。如果找不到說明綁定壓根沒裝進(jìn)這個解釋器環(huán)境按上面的源碼編譯步驟操作即可。確認(rèn)是否被其他同名包擾亂如果你之前用過系統(tǒng)的libselinux-python3那個包會產(chǎn)生/usr/lib/python3.6/site-packages/selinux目錄。部分自編譯Python的sys.path可能包含了這個系統(tǒng)路徑尤其是通過環(huán)境變量PYTHONPATH加進(jìn)去的導(dǎo)致解釋器嘗試加載一個ABI不匹配的.so文件這時反而會報(bào)更隱蔽的ImportError。檢查pyenv/虛擬環(huán)境的隔離性如果用pyenvpyenv which python3.12能看到路徑如果用venv要保證激活的狀態(tài)下執(zhí)行安裝。5.2 ImportErrorundefined symbol / dynamic module does not define module export function這個報(bào)錯基本是ABI不匹配的典型癥狀。比如你在Python 3.12里強(qiáng)行加載Python 3.6編譯的_selinux.cpython-36m-x86_64-linux-gnu.so就會報(bào)ImportError: /usr/lib/python3.6/site-packages/selinux/_selinux.cpython-36m-x86_64-linux-gnu.so: undefined symbol: PyUnicode_AsUTF8String或者SystemError: dynamic module does not define module export function (PyInit__selinux)遇到這個情況不要試圖用改名、復(fù)制文件的方式蒙混過關(guān)。cpython-36m文件名里面那個36m就是ABI標(biāo)識Python解釋器啟動時會校驗(yàn)它和當(dāng)前版本是否匹配不匹配直接拒絕。唯一的出路是重新編譯出cpython-312標(biāo)識的.so文件。5.3 RPM安裝時文件沖突/usr/lib/python2.7/site-packages/...有時候你想從RPM層面強(qiáng)制安裝某個版本的libselinux-python結(jié)果yum報(bào)文件沖突比如file /usr/lib/python2.7/site-packages/selinux/__init__.py from install of libselinux-python-2.5-15.el7.x86_64 conflicts with file from package libselinux-python-2.5-14.el7.x86_64這就說明系統(tǒng)里已經(jīng)存在同名的舊包占了路徑。不要輕易用rpm --force強(qiáng)制覆蓋那樣會讓RPM數(shù)據(jù)庫的文件清單混亂以后卸載或升級時容易殘留垃圾文件。應(yīng)該先用rpm -e把舊版本卸干凈再裝新的。5.4 RPM不識別自裝Python的路徑/usr/local/python3.8找不到selinux安裝完源碼編譯的selinux后運(yùn)行的是/usr/local/python3.8/bin/python3.8但是檢查RPM數(shù)據(jù)庫卻說沒有selinux相關(guān)的包這完全不奇怪。因?yàn)樵创a編譯安裝做的事情RPM數(shù)據(jù)庫根本不知道。不要試圖用RPM去管理源碼安裝的環(huán)境那是兩套完全獨(dú)立的文件分發(fā)體系。5.5 升級Python后舊site-packages殘留如果你把Python 3.8升級到3.9、3.10、3.11舊版本對應(yīng)site-packages里的selinux綁定會繼續(xù)存在但新解釋器不會自動去找舊版本的路徑。除非你把舊目錄手動加入PYTHONPATH但這種做法很容易埋雷因?yàn)椴煌“姹局g的ABI兼容性雖然沒有官方保證但也通常能用但不值得賭。正確做法是每升一個Python小版本就重新編譯一次selinux綁定或者干脆在用新版本之前先清理掉舊版本的selinux模塊避免誤加載。6. 個人體會與避坑清單這次折騰下來我最深的感受是RPM包管理器和Python解釋器本來就是兩個相對獨(dú)立的體系在多版本Python共存的機(jī)器上強(qiáng)行靠RPM解決一切Python依賴是行不通的。正確心態(tài)應(yīng)該是系統(tǒng)級包libselinux本體庫、編譯器、頭文件由RPM管Python特異性的擴(kuò)展綁定selinux模塊由Python解釋器各自管兩者分工明確互不越界。最后整理一份實(shí)用避坑清單都是我親手踩過的編譯selinux綁定時不要用python setup.py install這種省略解釋器路徑的寫法很可能調(diào)用了系統(tǒng)的Python 3.6。每次換Python版本先rm -rf build再編譯避免使用上一次編譯的中間產(chǎn)物。不要憑喜好隨意下載GitHub最新版libselinux源碼優(yōu)先選與系統(tǒng)libselinux庫版本一致或接近的tag。selinux.__file__的路徑必須和python -c import sys; print(sys.prefix)對應(yīng)否則說明裝錯了環(huán)境。處理完selinux之后順手檢查一下其他系統(tǒng)級Python擴(kuò)展比如systemd、dbus-python它們也可能面臨同樣的多版本兼容問題提前處理能省很多事。這套方法我已經(jīng)在多個生產(chǎn)環(huán)境驗(yàn)證從Python 3.8到3.12都能順利搞定。下次你再遇到同名RPM包沖突這一類問題不妨沉住氣先弄清包管理器管的路徑和解釋器找的路徑是不是同一個往往答案就清楚了一半。