https://realpython.com/python-modules-packages/
在軟體開發中,一個module是具有一些相關功能的軟體集合,比如,當你在開發一個遊戲時,可能會有一個模組負責game logic,而另一個module負責在螢幕上繪製對應的介面。每個module是一個不同的檔案,可以單獨編輯。
modules
python中每一個單獨的.py檔案就是一個module,模組的名稱就是檔案的名稱。一個module可以有一組函式,類或者變數。比如,上面說道的遊戲開發中,可能會有兩個檔案組成的兩個module:
# game.py # import the draw module import draw def play_game(): ... def main(): result = play_game() draw.draw_game(result) # this means that if this script is executed, then # main() will be executed if __name__ == '__main__': main()
在上面的game模組中,我們定義了play_game函式,它將使用定義在draw模組中的draw_game函式。而draw模組中實現draw_game對應的邏輯。
下面我們看看draw 模組長的樣子:
# draw.py def draw_game(): ... def clear_screen(screen): ...
在本例子中game module通過import draw來載入draw模組,而這將賦能game模組引用draw模組中實現的函式或者類。為了使用draw模組中的draw_game,我們需要通過dot點操作符告知game塊draw_game是哪個模組中的實現。
當import draw這個directive被執行時,python直譯器將從game.py檔案所在目錄開始尋找draw.py檔案,如果沒有找到,python直譯器將繼續在built-in內建模組中尋找。
你可能注意到當匯入一個module時,將會有一個.pyc檔案出現,這個檔案是一個編譯過的Python檔案。python直譯器將module檔案編譯成python的byte code以便不用每次Import時都需要重新解析他。如果已經有了.pyc檔案存在,則直接載入draw.pyc檔案,這個過程對於使用者來說是透明的。
importing module objects到當前的名稱空間namespace
我們可以通過from及import命令將對應模組的函式載入到主指令碼所在的名稱空間中:
# game.py # import the draw module from draw import draw_game def main(): result = play_game() draw_game(result)
通過上面的from, import後我們就不用再使用prefixmodule.function的方式來引用,而只需要function了,因為function已經存在於主指令碼的名稱空間裡面了!
import all objects from module
# game.py # import the draw module from draw import * def main(): result = play_game() draw_game(result)
自定義載入後的命名
# game.py # import the draw module if visual_mode: # in visual mode, we draw using graphics import draw_visual as draw else: # in textual mode, we print out text import draw_textual as draw def main(): result = play_game() # this can either be visual or textual depending on visual_mode draw.draw_game(result)
上面的例子中,使用as關鍵字,以及條件載入使得不同的module中定義的函式使用同一個名稱draw
module initialization
當一個module首次被載入時,對應module的程式碼將會執行一次以便初始化。如果其他的模組再次載入同一個module,則不會再重複載入!因此,module中的區域性變數就像一個singleton一樣,因為他們僅會初始化一次。
# draw.py def draw_game(): # when clearing the screen we can use the main screen object initialized in this module clear_screen(main_screen) ... def clear_screen(screen): ... class Screen(): ... # initialize main_screen as a singleton main_screen = Screen()
看上面的例子,main_screen就是首次載入draw模組式初始化的變數,不會重複初始化!
擴充套件module的載入路徑
在載入module時,除了預設的尋找路徑:主指令碼目錄以及內建module外,我們可以通過以下方法告知python直譯器,哪裡可以去尋找對應的module:
- PYTHONPATH變數:
- sys.path.append
PYTHONPATH=/foo python game.py sys.path.append("/foo") #在執行import之前執行該程式碼
built-in modules
和每個python發行版伴隨的有很多內建的Python庫,這些built-in modules使用C語言編寫,提供諸如訪問系統功能比如檔案I/O的功能,這些庫也提供一些常見問題的通用解決方案供呼叫。還有部分builtin模組用於抽象Python應用程式,以便和平臺無關。
https://docs.python.org/3/library/
比如,我們向使用urllib這個內建庫:
# import the library import urllib # use it urllib.urlopen(...)
我們可以通過dir函式來列出一個module中定義的函式:
>>> import urllib >>> dir(urllib) ['ContentTooShortError', 'FancyURLopener', 'MAXFTPCACHE', 'URLopener', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__version__', '_ftperrors', '_get_proxies', '_get_proxy_settings', '_have_ssl', '_hexdig', '_hextochr', '_hostprog', '_is_unicode', '_localhost', '_noheaders', '_nportprog', '_passwdprog', '_portprog', '_queryprog', '_safe_map', '_safe_quoters', '_tagprog', '_thishost', '_typeprog', '_urlopener', '_userprog', '_valueprog', 'addbase', 'addclosehook', 'addinfo', 'addinfourl', 'always_safe', 'basejoin', 'c', 'ftpcache', 'ftperrors', 'ftpwrapper', 'getproxies', 'getproxies_environment', 'getproxies_macosx_sysconf', 'i', 'localhost', 'main', 'noheaders', 'os', 'pathname2url', 'proxy_bypass', 'proxy_bypass_environment', 'proxy_bypass_macosx_sysconf', 'quote', 'quote_plus', 'reporthook', 'socket', 'splitattr', 'splithost', 'splitnport', 'splitpasswd', 'splitport', 'splitquery', 'splittag', 'splittype', 'splituser', 'splitvalue', 'ssl', 'string', 'sys', 'test', 'test1', 'thishost', 'time', 'toBytes', 'unquote', 'unquote_plus', 'unwrap', 'url2pathname', 'urlcleanup', 'urlencode', 'urlopen', 'urlretrieve']
當我們發現了我們希望使用的module中的某個function,還可以通過help命令來列出對應的幫助資訊.
help(urllib.urlopen)
pypi第三方modules
雖然python本身內建了非常豐富的package供程式設計師使用,但是依然有很多場景需要載入第三方的package,比如numpy, pandas等等。。
https://pypi.org/
開發package
packages是包含了多個package和module的名稱空間。簡單來說,package就是一些目錄,僅此而已。只要目錄中包含了一個命名為__init__.py的特殊檔案,我們就稱該目錄為一個package。這個檔案本身可以是空的,這個檔案的存在標識了該目錄為一個python package.
比如,如果我們建立一個目錄:foo,那麼foo作為package名稱,然後我們建立一個模組並命名為bar.py,我們不要忘記在foo目錄下增加一個__init__.py的檔案。那麼要使用這個bar模組,我們可以這樣做:
import foo.bar # 或者: from foo import bar
在__init__.py檔案中,我們可以指定哪些模組作為暴露的api,而其他的模組作為私有的:
__init__.py: __all__ = ["bar"]