在寫 Python 項目的時候,咱們可能常常會遇到導入模塊失敗的錯誤:ImportError: No module named 'xxx'
或者 ModuleNotFoundError: No module named 'xxx'
。html
導入失敗問題,一般分爲兩種:一種是導入本身寫的模塊(即以 .py 爲後綴的文件),另外一種是導入三方庫。本文主要討論第二種狀況,從此有機會,咱們再詳細討論其它的相關話題。python
解決導入 Python 庫失敗的問題,其實關鍵是在運行環境中裝上缺失的庫(注意是不是虛擬環境),或者使用恰當的替代方案。這個問題又分爲三種狀況:git
在編寫代碼的時候,若是咱們須要使用某個三方庫(如 requests),但不肯定實際運行的環境是否裝了它,那麼能夠這樣寫:github
try: import requests except ImportError: import os os.system('pip install requests') import requests
這樣寫的效果是,若是找不到 requests 庫,就先安裝,再導入。json
在某些開源項目中,咱們可能還會看到以下的寫法(以 json 爲例):緩存
try: import simplejson as json except ImportError: import json
這樣寫的效果是,優先導入三方庫 simplejson,若是找不到,那就使用內置的標準庫 json。網絡
這種寫法的好處是不須要導入額外的庫,但它有個缺點,即須要保證那兩個庫在使用上是兼容的,若是在標準庫中找不到替代的庫,那就不可行了。app
若是真找不到兼容的標準庫,也能夠本身寫一個模塊(如 my_json.py),實現想要的東西,而後在 except 語句中再導入它。ide
try: import simplejson as json except ImportError: import my_json as json
以上的思路是針對開發中的項目,可是它有幾個不足:一、在代碼中對每一個可能缺失的三方庫都 pip install,並不可取;二、某個三方庫沒法被標準庫或本身手寫的庫替代,該怎麼辦?三、已成型的項目,不容許作這些修改怎麼辦?tornado
因此這裏的問題是:有一個項目,想要部署到新的機器上,它涉及不少三方庫,可是機器上都沒有預裝,該怎麼辦?
對於一個合規的項目,按照約定,一般它會包含一個「requirements.txt 」文件,記錄了該項目的全部依賴庫及其所需的版本號。這是在項目發佈前,使用命令pip freeze > requirements.txt
生成的。
使用命令pip install -r requirements.txt
(在該文件所在目錄執行,或在命令中寫全文件的路徑),就能自動把全部的依賴庫給裝上。
可是,若是項目不合規,或者因爲其它倒黴的緣由,咱們沒有這樣的文件,又該如何是好?
一個笨方法就是,把項目跑起來,等它出錯,遇到一個導庫失敗,就手動裝一個,而後再跑一遍項目,遇到導庫失敗就裝一下,如此循環……(此處省略 1 萬句髒話)……
有沒有一種更好的能夠自動導入缺失的庫的方法呢?
在不修改原有的代碼的狀況下,在不須要「requirements.txt」文件的狀況下,有沒有辦法自動導入所須要的庫呢?
固然有!先看看效果:
咱們以 tornado 爲例,第一步操做可看出,咱們沒有裝過 tornado,通過第二步操做後,再次導入 tornado 時,程序會幫咱們自動下載並安裝好 tornado,因此再也不報錯。
autoinstall 是咱們手寫的模塊,代碼以下:
# 如下代碼在 python 3.6.1 版本驗證經過 import sys import os from importlib import import_module class AutoInstall(): _loaded = set() @classmethod def find_spec(cls, name, path, target=None): if path is None and name not in cls._loaded: cls._loaded.add(name) print("Installing", name) try: result = os.system('pip install {}'.format(name)) if result == 0: return import_module(name) except Exception as e: print("Failed", e) return None sys.meta_path.append(AutoInstall)
這段代碼中使用了sys.meta_path
,咱們先打印一下,看看它是個什麼東西?
Python 3 的 import 機制在查找過程當中,大體順序以下:
ImportError
異常其中要注意,sys.meta_path 在不一樣的 Python 版本中有所差別,好比它在 Python 2 與 Python 3 中差別很大;在較新的 Python 3 版本(3.4+)中,自定義的加載器須要實現find_spec
方法,而早期的版本用的則是find_module
。
以上代碼是一個自定義的類庫加載器 AutoInstall,能夠實現自動導入三方庫的目的。須要說明一下,這種方法會「劫持」全部新導入的庫,破壞原有的導入方式,所以也可能出現一些奇奇怪怪的問題,敬請留意。
sys.meta_path 屬於 Python 探針的一種運用。探針,即import hook
,是 Python 幾乎不受人關注的機制,但它能夠作不少事,例如加載網絡上的庫、在導入模塊時對模塊進行修改、自動安裝缺失庫、上傳審計信息、延遲加載等等。
限於篇幅,咱們再也不詳細展開了。最後小結一下:
參考資料:
https://github.com/liuchang0812/slides/tree/master/pycon2015cn
http://blog.konghy.cn/2016/10/25/python-import-hook/
https://docs.python.org/3/library/sys.html#sys.meta_path
公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。