導包寫包看這個就夠啦!!Python的模塊、包、庫
也僅僅是我的一些思考整理,可能會更新,歡迎指正!
[email protected]
文章目錄
在開發的時候,我們很容易使用一些別人的輪子來加快我們的開發進度,或者自己造一些輪子來更好的集成一些功能,這種輪子我們一般稱之爲第三方庫或者說第三方包
簡單的來講,例如我們使用import datetime就能夠導入名爲datetime的包(package)
# 導入package datetime
import datetime
# 獲取時間
print(datetime.datetime.ctime())
或者可以使用 from datetime import datetime
# 導入package datetime
from datetime import datetime
# 獲取時間
print(datetime.ctime())
如果僅僅使用,似乎也沒有什麼問題,但是大家有思考過import進去的到底是什麼麼?package datetime到底是一個文件夾?還是一個.py文件?大家有沒有碰到import進不去,只能from xxx import的情況? 自己寫的包卻提是Module不存在?
一、package和Module
1.1 什麼時package和Module
我們一般說第三方庫都不太在一package和Module的區別,事實上兩者的關係非常的自然。
當我們想實現某個功能時,我們創建.py文件來書寫代碼,這裏就是我們的主程序。
當我們功能實現比較複雜,代碼重複過多,我們就會把重複代碼打包,稱之爲函數。或者有面向對象的思維的話,可能會寫一個類。
當我們這個類或者函數可以被很多地方用到時,我們將這些函數和類統一整理到一個.py文件中,這樣我們就能使用import來導入這個文件,使用其中定義好的函數和類。這裏這個.py文件就是一個Module。一般簡單的Module就是包含一個類,類中定義了變量和操縱類的函數。這裏也就是簡單的一個輪子了。
但是複雜的功能實現可能會利用到非常多的類,例如flask,tensorflow等框架,會非常的複雜。爲了方便導入,開發者就會把這些文件整理成package。
**總結:**Module就是一個.py文件而package就是一個包含多個Module的包
1.2 package的特點
Module簡單的理解爲定義了一個或者多個class的.py文件就可以,那package有什麼特點呢?
package和dir文件夾非常像,圖標差別不大,就是多了一個小圈圈,最重要的是,多了一個__init__.py文件。很多博客裏面也講,多了__init__.py文件就是包了。這麼講也沒錯,但是沒有講清楚__init__.py文件是非常重要的。
二、import
2.1 import可以導入什麼?
import可以直接導入:
- package
- Module
- Module中定義的類或函數
事實上import導入的就是模塊,只不過很多我們看着不像是模塊,例如package,函數等,都可以理解成模塊。
2.2 import時發生了什麼
系統在導入模塊時,要做以下三件事:
- 爲源代碼文件中定義的對象創建一個名字空間,通過這個名字空間可以訪問到模塊中定義的函數及變量。
- 在新創建的名字空間裏執行源代碼文件。
- 創建一個名爲源代碼文件的對象,該對象引用模塊的名字空間,這樣就可以通過這個對象訪問模塊中的函數及變量
import的本質就是路徑搜索。告知程序我引入了那些Module,在編譯時會根據路徑對相應的程序進行編譯。使用高級IDE利用Pycharm等會對變量,導入等進行校驗。
import numpy as np
if __name__ == "__main__":
print(np.__name__)
'''
輸出:
numpy
'''
上面的小demo可以看到我們導入了numpy這個package。numpy是非常大的一個包,文件結構如下圖所示。
關鍵字 as 是將numpy命名爲np方便在編程時使用,但是調用np.__name__發現輸出仍然爲numpy,可見as僅僅是改變了numpy在本程序中指代的變量的名字,而np就是一個儲存着numpy路徑的變量,也就是一個引用。
2.3 爲什麼要使用from xxx import
之前講到,import本質是路徑搜索,搜索時候的順序是怎麼樣的呢?
- 模塊導入時,默認先在當前目錄下查找,然後再在系統路徑中查找
系統路徑查找的範圍:sys.path
import sys
if __name__ == "__main__":
# 打印系統路徑
print(sys.path)
# 添加系統路徑
sys.path.append("書寫絕對路徑")
將寫的package路徑添加到系統路徑中也是解決自己寫的模塊/包找不到的一個方法。
from的功能/優點
- 使用from語句可以將模塊中的對象直接導入到當前的名字空間. from語句不創建一個到模塊名字空間的引用對象,而是把被導入模塊的一個或多個對象直接放入當前的名字空間.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
這樣導入的僅僅是Module(.py文件)中的一部分內容,而不是全部。
在需要重複導入某個很大的包時,可以只導入一部分,減少開銷。
- 外一個from的功能就是尋找文件,import可能並不能搜索到你需要的包,但是可能你需要的包就在當前目錄下的一個子文件夾內,你就可以方便的進行引用,例如
from demo.demoPack.demoModule import hanshu
'''
|demo
|--__init__.py
|--test.py
|--demoPack
|--__init__.py
|--demoModule.py
demoModule.py
def hanshu():
print("hanshu")
return "yes"
class Printer:
def __init__(self):
print("init printer")
'''
三、Module參數
4.1 __all__
可以使用__all__=[‘名稱1’,‘名稱2’]來自定義 import * 可以導入的對象
4.2 __name__
每個模塊都擁有__name__屬性,它是一個內容爲模塊名字的字符串。最頂層的模塊名稱爲__main__。命令行或是交互模式下程序都運行在__main__ 模塊內部。
相信很多人都看過這樣的代碼
def main():
return 0
if __name__ == '__main__':
main()
這裏的if __name__ == ‘__main__’:可以簡單的理解爲程序的入口。也就是當這個程序作爲主程序時,.py文件作爲主程序,那麼其參數__name__ 就是 ‘__main__’。當作爲第三方包導入時,__name__ 就是.py的全限定類名。這樣的話就可以利用__name__來進行一些操作,例如:2.3中的例子。
'''
|demo
|--__init__.py
|--test.py
|--demoPack
|--__init__.py
|--demoModule.py
demoModule.py
def hanshu():
print("hanshu")
return "yes"
class Printer:
def __init__(self):
print("init printer")
def test(self):
print("run Printer.test()")
print(__name__)
if __name__ == 'demo.demoPack.demoModule':
print("import as Module...")
'''
# test.py
from demo.demoPack.demoModule import Printer
if __name__ == "__main__":
print("創建對象")
p = Printer()
print("調用方法")
p.test()
'''
輸出:
import as Module...
創建對象
init printer
調用方法
run Printer.test()
demo.demoPack.demoModule
'''
四、__init__.py文件的作用
4.1 將文件夾變爲一個package
前面我們說了,package和文件夾的一個重要區別就是當前路徑下是否存在__init__.py文件。__init__.py文件通常爲空文件,存在只是給編譯器的一個提醒。
注意:
__init__.py雖然常爲空,但是在導入package時,實際上時運行了__init__.py文件。這裏可以將整個package看作是一個class,而導入是調用了(或者說是創建,但是都不是正確的,只是可以這麼理解)這個class。class就會調用def __init__(self): 方法來初始化自己。
4.2 控制package的導入
和Module一樣,使用
# __init__.py
__all__ = ['os', 'sys', 're', 'urllib']
# a.py
from package import *
可以控制package中導入*時,可以導入的模塊
4.3 提前導入package
package可以時多級的,如果你想導入的模塊在非常深,但是你只能看見很外層的package,那你只能用from一層一層的進入更深的package然後導入。但是我們可以在當前package的__init__.py中提前導入一些包,這樣當你from當前的package,你可以直接import非常深的模塊(前提是你已經在__init__.py中導入過,還記得導入是運行__init__.py麼?)
4.4 固定全局參數(這一點也是我寫這篇文章的主要的原因。)
在使用logging包的時候,我發現只需要在主程序(__main__)中配置一次logging(例如創建多個不同的logger並設置不同的level和輸出方式等),在調用自己寫的模塊時,只要調用的模塊import logging,我就可以直接使用主程序創建的logger。總的就是,logging實現了全局的配置,就算再多個不同的.py文件中。
剛好我也需要自己創建一個可以在一個大項目中全局存在的唯一對象(java的單例模式)。
提到全局存在,調用一個,除了java的單例模式,其實很容易想到類中的靜態變量,但是創建的對象如何在多個文件中同時存在的問題還是沒有解決,知道我看到了這麼一段話。
其實表達的意思是,對應.py文件。但是我突然想到是不是整個package可以看成是一個對象呢(這也是我之前用類中def __init__(self)方法類比__init__.py文件的原因)。
找到了logging的源文件,logging的主要程序都寫在了__init__.py文件中,所以使用的時候import logging其實是導入了package而不是通常我們導入的Module。
__init__.py文件中,定義了
root就是默認存在的logger,level是WARNING,管理多個logger使用了Manager這個提前創建好的對象。
也就是直接__init__.py文件中的變量/對象,你不論怎麼在哪裏import package都是相同的,改變也是全局的
**注意:**這裏全局的意思是,如果你使用框架,而框架中實現logger的方式是基於logging的(例如flask就是對logging進行打包封裝到app對象中),那麼你在其他程序中對logging的配置,例如logging.basicConfig(),很有可能會影響到flask中的配置。