python modules and packages

世有因果知因求果發表於2018-08-02

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"]

 

相關文章