和其他大多數現代編程語言一樣,Python對包和模塊的下載、存儲以及管理有其自己的一套方法。Python的包一般存在幾個地方。比如,大部分系統包會存在sys.prefix指定的路徑下。在Mac OS X下這個路徑為:
>>> import sys
>>> sys.prefix
'/System/Library/Frameworks/Python.framework/Versions/3.5'
通常情況下我們更關心第三方包的安裝位置,比如easy_install或pip會將包存放在 site.getsitepackages所指定的路徑下:
>>> import site
>>> site.getsitepackages()
[
'/System/Library/Frameworks/Python.framework/Versions/3.5/Extras/lib/python',
'/Library/Python/3.5/site-packages'
]
這裡就帶來了一個問題,當我們同時開發多個工程時,不同的工程會將第三方的包存放在相同的路徑下。這就意味著,如果有兩個工程依賴同一個包,但是所需要的版本卻不一樣,比如工程A依賴v1.0.0,而工程B依賴v2.0.0。由於Python無法根據版本來區分包的安裝路徑,所以這裡就會發生版本衝突。這也就是本文所要介紹的虛擬環境(virtualenv/venv)所要解決的問題。
什麼是虛擬環境?
Python虛擬環境的主要目的是為了給不同的工程創建互相獨立的運行環境。在虛擬環境下,每一個工程都有自己的依賴包,而與其它的工程無關。不同的虛擬環境中同一個包可以有不同的版本。並且,虛擬環境的數量沒有限制,我們可以輕鬆地用virtualenv或者pyenv等工具來創建多個虛擬環境。
虛擬環境的使用
安裝虛擬環境工具的方法非常簡單,
$ pip install virtualenv
然後,我們創建一個名字叫“env”的虛擬環境:
$ virtualenv env
這條命令會自動創建一個叫“env”的目錄,並在其中生成如下的目錄結構:
├── bin
│ ├── activate
│ ├── activate.csh
│ ├── activate.fish
│ ├── easy_install
│ ├── easy_install-3.5
│ ├── pip
│ ├── pip3
│ ├── pip3.5
│ ├── python -> python3.5
│ ├── python3 -> python3.5
│ └── python3.5 -> /Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5
├── include
├── lib
│ └── python3.5
│ └── site-packages
└── pyvenv.cfg
這些目錄包括:
- bin: 用於管理虛擬環境的文件
- include: 編譯Python包時所需要的C頭文件
- lib: Python自帶及第三方的庫
這其中還包含有一些Python的工具和可執行文件等副本。這些文件用來保證Python代碼可以獨立於系統環境而運行。
bin目錄下有一個重要的腳本文件activate,這個腳本就是用來將其所在的虛擬環境設置為當前Python的運行環境:
$ source env/bin/activate
(env) $
可以看到,在運行完這行命令後,shell的提示符前會出現虛擬環境的名字,表示我們已經進入了這個環境中。
為了驗證這一點,我們以bcrypt模塊為例。首先我們要在全局系統環境中安裝這個模塊。在測試之前,我們需要先通過deactivate命令退出當前的虛擬環境:
(env) $ deactivate
$
現在我們的shell提示符迴歸到了正常狀態,同時Python的環境也切換到了全局的系統環境。
現在我們來安裝bcrypt並用它來生成一個密碼的hash值:
$ pip -q install bcrypt
$ python -c "import bcrypt; print(bcrypt.hashpw('password'.encode('utf-8'), bcrypt.gensalt()))"
$2b$12$vWa/VSvxxyQ9d.WGgVTdrell515Ctux36LCga8nM5QTW0.4w8TXXi
然後我們切換到虛擬環境並試圖運行同樣的命令:
$ source env/bin/activate
(env) $ python -c "import bcrypt; print(bcrypt.hashpw('password'.encode('utf-8'), bcrypt.gensalt()))"
Traceback (most recent call last):
File "", line 1, in
ImportError: No module named 'bcrypt'
可以看到,在虛擬環境中,bcrypt並沒有被安裝,說明虛擬環境的隔離作用是生效的。
虛擬環境是如何工作的?
瞭解虛擬環境背後的工作原理,對於一個Python開發者來說非常重要,它能夠幫助我們理解Python的運行環境,對依賴的解析等技術。為了解釋虛擬環境的原理,我們先來看一下在不同的環境中,Python可執行文件的位置有什麼不同。在默認的系統環境中查看python的路徑:
$ which python
/usr/bin/python
然後我們進入虛擬環境
$ source env/bin/activate
(env) $ which python
/Users/michaelherman/python-virtual-environments/env/bin/python
可以看到,在激活虛擬環境後,python可執行文件的路徑變成了當前環境目錄下的路徑,並且$PATH也發生了變化:
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:
$ source env/bin/activate
(env) $ echo $PATH
/Users/michaelherman/python-virtual-environments/env/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:
虛擬環境下的bin目錄排在了$PATH的最前面。這意味著,用戶在命令行中執行python的時候,將率先使用虛擬環境下的可執行文件,而不是系統默認的。
那麼虛擬環境下的python可執行文件與系統默認的有什麼不同呢?Python又是如何找到虛擬環境下的第三方庫的呢?
事實上,上面提到的python可執行文件之間並沒有什麼不同,但是它們所在的位置至關重要。在Python啟動的時候,它會獲取自身所在的路徑。然後這一路徑(bin的上一級)被設置到sys.prefix和sys.exec_prefix之中。在搜索第三方的site-packages時,搜索目錄是sys.prefix所指向的路徑下的lib/pythonX.X/site-packages/,其中X.X是Python的版本。
在前面的例子中,python文件所在路徑為/Users/michaelherman/python-virtual-environments/env/bin,因此sys.prefix會被設為/Users/michaelherman/python-virtual-environments/env,從而site-packages的路徑就變成了/Users/michaelherman/python-virtual-environments/env/lib/pythonX.X/site-packages。最後,這一路徑被存儲在sys.path數組中,其中包含著所有包的引用來源。
使用virtualenvwrapper管理虛擬環境
虛擬環境的引入解決了我們關於環境衝突的問題,但是它同時也帶來了一個問題,就是虛擬環境過多所帶來的管理問題。virtualenvwrapper就是專門用來解決虛擬環境管理問題的一個工具。我們可以很方便地用它來實現對虛擬環境的創建,刪除,拷貝,並且可以輕鬆地在不同環境間進行切換。
virtualenvwrapper的安裝非常簡單:
$ pip install virtualenvwrapper
安裝後,我們需要將virtualenvwrapper.sh腳本添加到shell啟動文件中:
$ which virtualenvwrapper.sh
/usr/local/bin/virtualenvwrapper.sh
在shell啟動文件~/.bashrc中添加:
export WORKON_HOME=$HOME/.virtualenvs # Optional
export PROJECT_HOME=$HOME/projects # Optional
source /usr/local/bin/virtualenvwrapper.sh
並運行
$ source ~/.bashrc
然後就會在$WORKON_HOME所在的路徑下創建集中存放虛擬環境的目錄。
接著,我們就可以用下面的一系列命令虛擬環境進行管理:
workon
deactivate
mkvirtualenv
cdvirtualenv
rmvirtualenv
比如,我們要創建一個新的工程:
$ mkvirtualenv my-new-project
(my-new-project) $
此時會在$WORKON_HOME下創建一個新的虛擬環境。
退出一個虛擬環境的方法和之前一樣:
(my-new-project) $ deactivate
$
如果你有多個虛擬環境,可以對它們進行查看:
$ workon
my-new-project
my-django-project
web-scraper
並選擇其中一個進行激活:
$ workon web-scraper
(web-scraper) $
這樣,你就不再需要記住每一個環境存放的具體位置,可以輕鬆的對他們進行刪除、拷貝、切換,是不是非常方便?
好了,關於虛擬環境的介紹就到這裡,獲取更多精彩內容敬請關注。