1 前言
導(dǎo)包這個(gè)詞我相信編程人員不會(huì)陌生。如何很好地在Python中導(dǎo)入別人的包以及自己寫(xiě)的工具函數(shù)?這時(shí)需要分清楚和用好的,特此總結(jié)以饗讀者。
2 優(yōu)雅地導(dǎo)入別人的包
當(dāng)然這里主要指你使用pip(conda)安裝到Python環(huán)境中的包。這里導(dǎo)包就很簡(jiǎn)單了,因?yàn)樵赑ython解釋器在解釋程序時(shí)會(huì)在系統(tǒng)中掃描相關(guān)包的路徑,不至于找不到。例如你安裝了NumPy包,你可以這樣導(dǎo)入:
import numpy
但是在寫(xiě)Python程序時(shí),你的程序會(huì)顯得繁瑣,在Python編程社區(qū)中,我們通常會(huì)為其設(shè)置一個(gè)簡(jiǎn)稱(chēng),如:
import numpy as np
這樣在程序中代碼會(huì)簡(jiǎn)潔一些,別人看到np也會(huì)知道你導(dǎo)的包是numpy,這已經(jīng)是行業(yè)通用的習(xí)慣了。
有時(shí)候一個(gè)工具包會(huì)有多級(jí)工具,或者說(shuō)一個(gè)py文件下會(huì)有多個(gè)函數(shù),假如你想使用numpy中的sin()函數(shù),你可以使用這種方式導(dǎo)入:
from numpy import sin
如果你想sin、cos()、tan()等函數(shù),你也可以這樣使用通配符“*”導(dǎo)入:
from numpy import *
也就是說(shuō)你把numpy下所有函數(shù)導(dǎo)入,雖然可以像往常的時(shí)候那樣使用,但是并不建議,最好是需要什么函數(shù),你就導(dǎo)入什么函數(shù)。需要補(bǔ)充的是:這個(gè)“*”所導(dǎo)入的函數(shù)都在對(duì)應(yīng)包的__init__.py文件中的__all__ = ['module_1', 'module_2']中。
除此之外,還需要注意的是,不僅numpy下有sin函數(shù),系統(tǒng)的math函數(shù)也是有sin函數(shù)的,如果你的程序報(bào)錯(cuò)了也需要檢查一下,特別是在使用from numpy import *。如果兩個(gè)函數(shù)的輸入和輸出是相同的還好,如果兩個(gè)函數(shù)功能有些差異等就可能報(bào)錯(cuò),如果必須的導(dǎo)入的話(huà),就為對(duì)應(yīng)的函數(shù)起一個(gè)別名,如:
from numpy import sin as np_sin
或者不導(dǎo)入具體的函數(shù),使用下面的方式:
import numpy as np
np.sin(10)
這種方式看起來(lái)也是比較簡(jiǎn)單明了的。
3 優(yōu)雅地使用自己寫(xiě)的函數(shù)包
上面導(dǎo)包方式還是比較簡(jiǎn)單的,但是導(dǎo)入自己的包還是會(huì)經(jīng)常出錯(cuò)的。有時(shí)候我們會(huì)自己寫(xiě)一些工具函數(shù),但是每每調(diào)用是總會(huì)出現(xiàn)不同的錯(cuò)誤,我們看看如何優(yōu)雅地導(dǎo)入自己寫(xiě)的函數(shù),或者跨文件夾的文件。在此之前先看看當(dāng)前測(cè)試項(xiàng)目的目錄結(jié)構(gòu)和幾個(gè)路徑相關(guān)函數(shù)。
3.1 項(xiàng)目目錄結(jié)構(gòu)和幾個(gè)路徑相關(guān)的函數(shù)
當(dāng)前項(xiàng)目的結(jié)構(gòu)如下:
下面來(lái)看看幾個(gè)路徑,有如下程序:
import os
import sys
def main():
absPath = os.path.abspath(__file__) # 返回運(yùn)行當(dāng)前程序py文件的路徑
print('absPath', absPath)
temPath = os.path.dirname(absPath) # 往上返回一級(jí)目錄,得到文件所在的路徑
print('temPath', temPath)
temPath = os.path.dirname(temPath) # 在往上返回一級(jí),得到文件夾所在的路徑
print('temPath', temPath)
print(sys.path) # Python解釋器查找Python包路徑有哪些(Python解釋器安裝地址因人而異)
if __name__ == "__main__":
main()
運(yùn)行結(jié)果如下:
absPath f:testTempmain.py
temPath f:testTemp
temPath f:test
['f:\test\Temp', 'C:\Development\Python\Anaconda3\python37.zip', 'C:\Development\Python\Anaconda3\DLLs', 'C:\Development\Python\Anaconda3\lib', 'C:\Development\Python\Anaconda3', 'C:\Development\Python\Anaconda3\lib\site-packages', 'C:\Development\Python\Anaconda3\lib\site-packages\win32', 'C:\Development\Python\Anaconda3\lib\site-packages\win32\lib', 'C:\Development\Python\Anaconda3\lib\site-packages\Pythonwin']
可以看到這時(shí)的項(xiàng)目路徑變成了'f:\test\Temp\model1',也就是說(shuō)一個(gè)項(xiàng)目的主程序(自己總結(jié),方便理解) 在運(yùn)行時(shí)就決定了整個(gè)項(xiàng)目的路徑,這為后面的導(dǎo)包提供了依據(jù)。需要注意的是導(dǎo)入自寫(xiě)的包后,自寫(xiě)的包被調(diào)用時(shí),其整個(gè)項(xiàng)目路徑依然是項(xiàng)目入口程序那個(gè)py文件所對(duì)應(yīng)的路徑(這個(gè)py文件所處的文件夾)。知道這個(gè)后,我們看看以下各種導(dǎo)包情況如何解決,默認(rèn)只執(zhí)行main.py這個(gè)文件,也就是說(shuō)確定了這個(gè)項(xiàng)目路徑(main.py調(diào)用其他模塊的包)。
3.2 程序入口文件調(diào)用各個(gè)模塊的包
因?yàn)槟J(rèn)只執(zhí)行main.py這個(gè)文件,這里以main.py文件調(diào)用model1中m1_test1.py的model1_test1()函數(shù)為例,看看如何調(diào)用,示例程序如下:
其中m1_test1.py的程序如下:
import sys
def model1_test1():
print('model1_test1 is been called!')
if __name__ == "__main__":
print(sys.path)
項(xiàng)目入口函數(shù)main.py代碼如下:
import os
import sys
import model1.m1_test1 as m1t1
def main():
m1t1.model1_test1()
if __name__ == "__main__":
main()
程序運(yùn)行結(jié)果:
model1_test1 is been called!
因?yàn)轫?xiàng)目路徑是在main.py所在的文件夾,而model1又與main.py處于同一文件級(jí)別,則在調(diào)用model1模塊下的py文件時(shí)可以直接從model1開(kāi)始書(shū)寫(xiě)。也需要注意 ,調(diào)用model1_test1.py文件時(shí), model1.m1_test1 中的 m1_test1 為對(duì)應(yīng)py文件的文件名。
3.3 同級(jí)文件自寫(xiě)py文件引用
以model1模塊為例,用m1_test1.py調(diào)用m1_test2.py中的model1_test2()函數(shù),在3.2的基礎(chǔ)上,為model1_test1.py修改成如下代碼:
3.3 同級(jí)文件自寫(xiě)py文件引用
以model1模塊為例,用m1_test1.py調(diào)用m1_test2.py中的model1_test2()函數(shù),在3.2的基礎(chǔ)上,為model1_test1.py修改成如下代碼:
其中m1_test2.py文件中的代碼如下:
def model1_test2():
print('model1_test2 is been called!')
可以看出,我們?cè)趍ain.py中去運(yùn)行時(shí),即使兩個(gè)文件在同一級(jí)在導(dǎo)包時(shí)也需要從model1開(kāi)始書(shū)寫(xiě),如果寫(xiě)成這樣import m1_test2 as m1t2的話(huà),會(huì)出現(xiàn):
Traceback (most recent call last):
File "f:testTempmain.py", line 3, in <module>
import model1.m1_test1 as m1t1
File "f:testTempmodel1m1_test1.py", line 2, in <module>
import m1_test2 as m1t2
ModuleNotFoundError: No module named 'm1_test2'
如果兩個(gè)同級(jí)文件所處深度太深,例如m1_test1.py和m1_test2.py文件在model1/sum_model/sub_sub_model目錄下,如果從model1開(kāi)始寫(xiě),那導(dǎo)包語(yǔ)句豈不是特別長(zhǎng),這時(shí)可以使用以下方式進(jìn)行修改:
import sys
sys.path.Append('model1/')
import m1_test2 as m1t2
當(dāng)你想測(cè)試m1_test2.py文件函數(shù)時(shí)(直接運(yùn)行m1_test1.py,即將m1_test1.py所在文件夾當(dāng)做項(xiàng)目文件路徑)這種sys.path導(dǎo)入方式也不會(huì)影響程序的運(yùn)行。
3.4 跨模塊導(dǎo)入包
現(xiàn)在我們的程序保持的狀態(tài)是使用sys.path解決model1中m1_test1.py導(dǎo)入m1_test2 .py的方式。現(xiàn)在我們看看在model1中py文件如何使用model2模塊中的py文件。這里我可以想到使用如下導(dǎo)入方式:
import model1.m1_test2 as mt2
這種方式就行了嘛。現(xiàn)在我們來(lái)看看實(shí)際是不是這樣的,main.py文件修改如下:
import os
import sys
import model2.m2_test1 as m2t1
def main():
m2t1.model2_test1()
if __name__ == "__main__":
main()
m2_test1.py程序如下:
import model1.m1_test2 as m1t2
def model2_test1():
print('model2_test1 is been called!')
if __name__ == "__main__":
m1t2()
運(yùn)行main.py文件查看結(jié)果:
model2_test1 is been called!
結(jié)果正是我們估計(jì)的。如果你猜到這么做了,基本上已經(jīng)理解整個(gè)導(dǎo)包的流程。 但想使用sys.path改進(jìn),即m2_test1.py程序如下:
import sys
sys.path.append('model1/')
import m1_test2 as m1t2
def model2_test1():
print('model2_test1 is been called!')
if __name__ == "__main__":
m1t2()
對(duì)于在main.py運(yùn)行時(shí),結(jié)果正常,但是,直接運(yùn)行m2_test1.py程序時(shí),就會(huì)出錯(cuò),出錯(cuò)結(jié)果可想而知,除非你把model1的全路徑添加到sys.path中。
3.5 讓自寫(xiě)包變得更加標(biāo)準(zhǔn)
如果讓你的包變得更加標(biāo)準(zhǔn)的話(huà),這時(shí)就需要在對(duì)應(yīng)的模塊下添加__init__.py文件了,這個(gè)文件中的內(nèi)容可以什么都沒(méi)有,但會(huì)告訴解釋器,這個(gè)文件所在文件夾就是一個(gè)標(biāo)準(zhǔn)的包了。可以通過(guò)配置__init__.py文件限制from module import *中可以導(dǎo)入的py文件等。
3.6 讀取文件
在實(shí)際的開(kāi)發(fā)過(guò)程中,也可能會(huì)出現(xiàn)讀取同級(jí)文件,或跨包文件。類(lèi)似于包,讀取文件的操作則有所不同。讀取同級(jí)文件如下,把main.py文件修改如下:
import os
import sys
import model2.m2_test1 as m2t1
def main():
m2t1.read_file()
if __name__ == "__main__":
main()
m2_test1.py添加如下函數(shù)即可(絕對(duì)路徑方式):
def read_file():
import os
import sys
file_path = 'm2_file.txt'
abs_path = os.path.abspath(__file__) # 獲取當(dāng)前py文件路徑
dir_path = os.path.dirname(abs_path) # 獲取當(dāng)前py文件所在目錄路徑
file_path = os.path.join(dir_path, file_path) # 拼接
with open(file_path) as reader:
print(reader.read())
讀取跨包的文件方法如下(使用項(xiàng)目路徑定位),修改main.py文件代碼如下:
import os
import sys
import model1.m1_test2 as m1t2
def main():
m1t2.read_file()
if __name__ == "__main__":
main()
修改m1_test2.py代碼如下:
def model1_test2():
print('model1_test2 is been called!')
def read_file():
import os
project_root = os.getcwd() # 獲取項(xiàng)目根目錄
file_path = 'm2_file.txt'
file_path = os.path.join(project_root, 'model2', file_path)
with open(file_path) as reader:
print(reader.read())
運(yùn)行main.py文件即可。
4 總結(jié)
總的來(lái)說(shuō),上面的方法能夠應(yīng)付大多導(dǎo)包和讀取文件的問(wèn)題。但在實(shí)際開(kāi)發(fā)過(guò)程中,還需要靈活運(yùn)用,避免按圖索驥,生搬硬套。