之前一直比較抵觸用 Python ,很大一部分原因是覺得 Python 項目的環境管理比較混亂。Node.js 有 Npm 包管理工具,通過 package.json 配置項目依賴,最多再通過 nvm 來進行環境切換;Java 有 Maven Gradle 來進行包管理和項目依賴配置,並體現在 pom.xml 和 build.gradle 等中。而 Python 相比編程語言有時更體現了腳本語言的特性,系統化和標准化程度都不太高。很多 Python 項目上來就是怼代碼,沒有聲明依賴、配置環境的文件。這樣的好處是簡單項目堆砌起來非常快,但是一旦代碼量上了規模,依賴管理、環境配置、項目啟動等就到處都是坑。
可是稍微了解了一下後發現其實 Python 不止能當腳本語言來用。基於一定的工具鏈,Python 也能寫出漂亮標准的項目代碼、將環境和依賴理的明明白白。
最基礎的依賴管理應當能解決如下問題:
能快速配置好項目依賴,搭建好開發環境。
明確知道當前項目依賴了哪些第三方的包,以及他們的依賴樹。
能快速添加和移除給定的依賴,進行依賴調解。
這些功能使用 Pip 工具鏈其實是能很方便做到的。
快速配置環境(pip)
想簡單預覽當前環境下的依賴包可以直接用 pip list 命令:
$ pip list
Package Version
---------- -------------------
certifi 2020.6.20
pip 19.3.1
setuptools 44.0.0.post20200106
wheel 0.36.2
對於一個空的 Python 環境,基礎一般只會有這四個包。我們這樣就知道了當前環境中有哪些包,以及他們的版本。
為了方便說明,我們先多引一些依賴 pip install flask 。
$ pip list
Package Version
------------ -------------------
certifi 2020.6.20
click 7.1.2
Flask 1.1.2
itsdangerous 1.1.0
Jinja2 2.11.3
MarkupSafe 1.1.1
pip 19.3.1
setuptools 44.0.0.post20200106
Werkzeug 1.0.1
wheel 0.36.2
安裝了 Flask 之後,我們發現除了 Flask 他還多引入了好多個間接依賴。
如果想要將這個信息記錄下來,我們可以用 pip freeze 命令,記在 requirements.txt 中(一個約定俗成的名字)。
$ pip freeze > requirements.txt
$ cat requirements.txt
certifi==2020.6.20
click==7.1.2
Flask==1.1.2
itsdangerous==1.1.0
Jinja2==2.11.3
MarkupSafe==1.1.1
Werkzeug==1.0.1
好了,記下這個文件,以後我們如果需要在一個新的 Python 環境中引入當前的依賴,只需要使用 pip install -r requirements.txt 即可。
明確項目依賴(pipdeptree)
pip list 或 pip freeze 打印出來的依賴有一個問題,就是並沒有明確依賴關系。這樣的壞處是,當我們想清理依賴的時候,就不知道到底哪些依賴是能被直接刪除的、哪些依賴又是被間接依賴而不能輕易刪除的。
例如我們可能在項目中用了 Flask ,但是我們可能不知道 Flask 也引用了 Jinja2 。這是我們如果擅自刪除了 Jinja2 ,項目就可能跑不起來。
這時就可以使用 pipdeptree 工具來管理依賴樹:
$ pip install pipdeptree
...
$ pipdeptree
certifi==2020.6.20
Flask==1.1.2
- click [required: >=5.1, installed: 7.1.2]
- itsdangerous [required: >=0.24, installed: 1.1.0]
- Jinja2 [required: >=2.10.1, installed: 2.11.3]
- MarkupSafe [required: >=0.23, installed: 1.1.1]
- Werkzeug [required: >=0.15, installed: 1.0.1]
pipdeptree==2.0.0
- pip [required: >=6.0.0, installed: 19.3.1]
setuptools==44.0.0.post20200106
wheel==0.36.2
現在我們就知道了,原來 Jinja2 是被 Flask 依賴的,這樣我們就不會隨便刪除了。
項目依賴治理(pip-autoremove)
那麼問題來了,如果我忽然不想依賴 Flask 了,我們需要怎麼做呢?
無腦的做法是 pip uninstall flask -y 。不那麼顯然的是,這其實不夠優雅:
$ pip uninstall flask -y
...
$ pipdeptree
certifi==2020.6.20
click==7.1.2
itsdangerous==1.1.0
Jinja2==2.11.3
- MarkupSafe [required: >=0.23, installed: 1.1.1]
pipdeptree==2.0.0
- pip [required: >=6.0.0, installed: 19.3.1]
setuptools==44.0.0.post20200106
Werkzeug==1.0.1
wheel==0.36.2
發現沒,Flask 雖然被卸載了,但是他的依賴包並沒有卸載干淨。你可能需要重新一個一個判斷你是否需要剩下的包,然後再遞歸刪除。
幸運的是,我們就可以用 pip-autoremove 工具來做這件事。我們重新安裝Flask,再用這個工具刪除試試:
$ pip install flask
$ pip install pip-autoremove
$ pip-autoremove flask -y
$ pipdeptree
certifi==2020.6.20
pip-autoremove==0.9.1
pipdeptree==2.0.0
- pip [required: >=6.0.0, installed: 19.3.1]
setuptools==44.0.0.post20200106
wheel==0.36.2
這下干淨了。
pip 能基本解決單一項目的環境處理問題。但是由於 Python 是全局環境,如果有多個項目,我們就無法區分項目維度的依賴。解決這個問題一般有兩個思路,一個是像 Node.js 一樣用 package.json 配置文件支持項目維度的環境隔離,另一個就是走 rvm、nvm的思路用虛擬環境隔離。目前看 Python 只能支持後者,也就是用基於 Conda 的虛擬環境。
值得一提的是,conda 雖然為Python 而生,但他其實是一個通用的虛擬環境工具。他的官網寫的很清楚:
Package, dependency and environment management for any language---Python, R, Ruby, Lua, Scala, Java, JavaScript, C/ C++, FORTRAN
Conda is an open-source package management system and environment management system that runs on Windows, macOS, and Linux. Conda quickly installs, runs, and updates packages and their dependencies. Conda easily creates, saves, loads, and switches between environments on your local computer. It was created for Python programs but it can package and distribute software for any language.
很強大,有多強大,可以將不同語言的依賴環境整合在一起的強大。
安裝
Conda 官網給了兩個發行版本,一個是 Anaconda ,一個是 Miniconda。Anaconda 相比 Miniconda 主要是多預裝了很多科學計算的庫,而我更喜歡按需使用不喜歡全家桶,所以我選 Miniconda。
官網下載miniconda3,並執行安裝腳本。
安裝後會發現 .bashrc 下多了幾行:
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/home/zhenping/miniconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
if [ -f "/home/zhenping/miniconda3/etc/profile.d/conda.sh" ]; then
. "/home/zhenping/miniconda3/etc/profile.d/conda.sh"
else
export PATH="/home/zhenping/miniconda3/bin:$PATH"
fi
fi
unset __conda_setup
# <<< conda initialize <<<
重新登錄,或著手動執行 source ~/.bashrc ,以加載conda命令。
現在就會發現提示符前多了默認環境 (base),表示當前啟用了默認環境 base 。
如果不想在會話啟動時就開啟conda環境,就執行 conda config --set auto_activate_base false 。
環境操作
創建一個純淨的 Python2.7 環境,名字姑且叫 frida ,並激活該環境。
$ conda create -n frida python=2.7 -y
...
$ conda activate frida
需要注意的是,創建環境之後,一定要 activate 該環境,否則後續的 install 操作還是在 base 環境。
查看已有環境列表:
(frida) $ conda env list
# conda environments:
#
base /home/myths/miniconda3
frida * /home/myths/miniconda3/envs/frida
查看當前環境下的依賴:
(frida) $ conda list
# packages in environment at /home/myths/miniconda3/envs/frida:
#
# Name Version Build Channel
_libgcc_mutex 0.1 main
ca-certificates 2021.4.13 h06a4308_1
certifi 2020.6.20 pyhd3eb1b0_3
libffi 3.3 he6710b0_2
libgcc-ng 9.1.0 hdf63c60_0
libstdcxx-ng 9.1.0 hdf63c60_0
ncurses 6.2 he6710b0_1
pip 19.3.1 py27_0
python 2.7.18 h15b4118_1
readline 8.1 h27cfd23_0
setuptools 44.0.0 py27_0
sqlite 3.35.4 hdfb4753_0
tk 8.6.10 hbc83047_0
wheel 0.36.2 pyhd3eb1b0_0
zlib 1.2.11 h7b6447c_3
我們發現,與 pip list 只展示 Python 包不同,conda list 還展示了對其他語言項目代碼的依賴。
退出環境:
(frida) $ conda deactivate
這裡需要注意,conda 的環境是可以默認嵌套兩層的,因此 deactivate 的時候要看清楚了,可能要 deactivate 兩次才能真正退出 Conda 。
依賴管理
Conda 也有和 pip freeze 類似的依賴管理方式:
為當前環境創建配置文件:
(frida) $ conda env export > environment.yaml
(frida) $ cat environment.yaml
name: frida
channels:
- defaults
dependencies:
- _libgcc_mutex=0.1=main
- ca-certificates=2021.4.13=h06a4308_1
- certifi=2020.6.20=pyhd3eb1b0_3
- libffi=3.3=he6710b0_2
- libgcc-ng=9.1.0=hdf63c60_0
- libstdcxx-ng=9.1.0=hdf63c60_0
- ncurses=6.2=he6710b0_1
- pip=19.3.1=py27_0
- python=2.7.18=h15b4118_1
- readline=8.1=h27cfd23_0
- setuptools=44.0.0=py27_0
- sqlite=3.35.4=hdfb4753_0
- tk=8.6.10=hbc83047_0
- wheel=0.36.2=pyhd3eb1b0_0
- zlib=1.2.11=h7b6447c_3
prefix: /home/myths/miniconda3/envs/frida
根據配置文件復現當前環境:
$ conda env create -f environment.yaml
IDE集成
使用 conda 還有個很大的好處就是和 IDE 可以非常方便的集成。
用Conda做其他語言的虛擬環境方便麼?
現在看起來非常方便,幾乎所有需要區分全局環境的地方都可以用。比如 Java 環境:
$ conda create -n java8
$ conda activate java8
$ conda install openjdk=8.0.152 -y
$ conda list
# packages in environment at /home/myths/miniconda3/envs/java8:
#
# Name Version Build Channel
openjdk 8.0.152 h7b6447c_3
同時,我們也可以在這個環境中集成 Node 環境,Python 環境,Ruby環境,甚至集成一些 curl、wget 等常用命令,非常方便。這對於一些跨語言、跨環境項目的環境搭建可是太有幫助了。
如何找conda支持的包呢?
可以直接用 conda search xxx 來搜索。不過這樣可能不太全,我們也可以在 https://anaconda.org/search?q=openjdk 這裡根據關鍵字搜索,當然也可以向這裡貢獻。
安裝 Python 包是用 conda 好還是用 pip 好?
如果明確是純粹的 python 包,還是建議用 pip install 安裝,方便用 pip 統一管理。對於跨語言的、或者是本身就整合了各種依賴的環境(比如 tenserflow),再考慮用 conda install。
anaconda-vs-miniconda
conda官方文檔
Pip-deptree
Pip-autoremove
-END-
掃碼添加請備注:python,進群與宋老師面對面交流:517745409