Python模組高階技巧

veelion發表於2019-02-24

上一節,我們講解了Python模組的基礎知識,這一節我們繼續深入瞭解模組的更多知識,從而讓大家全面瞭解、掌握和運用模組到我們實際的程式設計中。

在上一節中有一句話“接著我們在這個檔案所在目錄執行Python直譯器IPython”,不知道大家還記不記得。這就話背後隱含的意思是,在這個檔案(模組)目錄下,我們才可以import到這個模組,否則會報錯,說找不到這個模組。這裡,就涉及到了Python模組的搜尋路徑。

Python模組高階技巧

Python模組的搜尋路徑

當一個名為mylib的模組被匯入時,直譯器首先搜尋內建模組是不是有該名字的模組。
如果沒有找到,接著搜尋sys.path列出的目錄下面是不是有名為mylib的模組。

sys.path的初始化按以下幾個路徑的順序:

(1)包含輸入指令碼的目錄,如果沒有輸入指令碼則是當前目錄;
(2)環境變數PYTHONPATH(一個目錄名稱的列表);
(3)Python庫的安裝目錄。

下面我們來驗證一下sys.path所包含的路徑,編寫一個Python檔案initpath.py如下:

# Author: veelion
# file: initpath.py

import sys
print('\n'.join(sys.path))

命令列下執行:python3 initpath.py得到如下結果:

$ python mylib.py
/home/veelion/p2/tutorial/md_Python/codes
/home/veelion/.virtualenvs/py3.7/lib/python37.zip
/home/veelion/.virtualenvs/py3.7/lib/python3.7
/home/veelion/.virtualenvs/py3.7/lib/python3.7/lib-dynload
/usr/lib/python3.7
/home/veelion/.virtualenvs/py3.7/lib/python3.7/site-packages

我們可以發現,initpath.py所在的目錄是sys.path列表的第一個元素。符合上面三條原則的順序。

接下來我們透過互動式Python直譯器來看看sys.path,執行CPython直譯器再匯入sys:

>>> import sys
>>> sys.path
['', '/home/veelion/.virtualenvs/py3.7/lib/python37.zip', '/home/veelion/.virtualenvs/py3.7/lib/python3.7', '/home/veelion/.virtualenvs/py3.7/lib/python3.7/lib-dynload', '/usr/lib/python3.7', '/home/veelion/.virtualenvs/py3.7/lib/python3.7/site-packages']

細心的小猿可以發現,sys.path的第一項是個空字串,和執行指令碼方式下的第一項——當前路徑不一樣。為什麼會是空字串呢?

這是因為,當互動式執行Python直譯器時(或者指令碼是從標註輸入讀取的),可以認為傳給直譯器的指令碼檔案路徑為空,那麼就把sys.path[0]設定為空字串,它告訴Python搜尋模組時先從當前資料夾開始。

以上兩種方法驗證的sys.path都符合預期,然而IPython有點例外。

In [1]: import sys

In [2]: sys.path
Out[2]: 
['/home/veelion/.virtualenvs/py3.7/bin',
 '/home/veelion/.virtualenvs/py3.7/lib/python37.zip',
 '/home/veelion/.virtualenvs/py3.7/lib/python3.7',
 '/home/veelion/.virtualenvs/py3.7/lib/python3.7/lib-dynload',
 '/usr/lib/python3.7',
 '',
 '/home/veelion/.virtualenvs/py3.7/lib/python3.7/site-packages',
 '/home/veelion/.virtualenvs/py3.7/lib/python3.7/site-packages/IPython/extensions',
 '/home/veelion/.ipython']

IPython 並沒有把當前路徑放在第一項。這樣就會導致你寫的模組與系統模組重名時,它import的是系統模組而不是你寫的模組,而前面兩種方式就是匯入你寫的模組而非系統模組。這一點在使用IPython時要格外注意。

注意你可以在程式中修改sys.pathsys.path是一個Python的列表結構,我們可以像修改列表那樣修改它,增加、刪除、修改路徑順序。比如,可以透過sys.path.insert(0, 'my-module-path')來把我們自己寫的模組的路徑放到搜尋路徑的最前面,優先搜尋自己的模組。

編譯後的Python檔案:*pyc

為了加速模組的載入時間,Python會快取已經編譯好的模組,並把它們放在與模組同級目錄下的__pycache__資料夾下面,編譯好的模組的命名方式為:module.version.pyc,其中的version包含Python的版本號。比如:

$ ls __pycache__/
m1.cpython-36.pyc  m2.cpython-36.pyc

cpython-36就是編譯這個模組的Python資訊:用CPython 3.6 進行編譯的。這種命名方式方便不同版本的Python編譯的模組同時存在而不造成衝突。

Python在兩種情況下不檢查快取。
其一,它總是重新編譯並且不儲存直接從命令列載入的模組的結果。
其二,如果沒有模組原始碼檔案,它不會檢查快取。要支援非源(僅編譯)分發,已編譯的模組必須位於原始碼目錄中,並且不得有模組原始碼。

舉個例子理解一下這兩點:
(1)如果在命令列下執行python m1.py,Python總是從新編譯m1.py,但不會儲存pyc檔案,因為每次都有重新編譯就沒必要儲存了。
(2)如果我們匯入m1模組時,搜尋路徑目錄下只有m1.pyc而沒有m1.py檔案,那就直接匯入m1.pyc。這種方式適合把編譯好的pyc釋出給其他人而不是給它們原始碼,使用這種方式時,把.pyc檔案從__pycache__中複製到.py檔案相同的目錄下並刪掉.py檔案即可。

Python模組的高階技巧

(1)模組compileall可以把一個資料夾下所有的py檔案編譯成.pyc檔案。
它的使用很簡單,命令列執行的格式如下:
python -m compileall 資料夾或檔名
更多選項可以透過:python -m compileall -h檢視。

(2)編譯成.pyc檔案時,可以給Python命令兩個選項:-O-OO,使得編譯後的檔案更小。
-O 去除assert語句;
-OO 去除assert語句和__doc__ string
根據情況來使用這兩個選項,用compileall編譯檔案時加這個選項就是這樣子的:
python -O -m compileall 資料夾或檔名

生成的pyc檔名稱裡面有opt-標籤,-O的標籤是opt--OO的標籤就是opt-2。比如:

$ ls -F -1 __pycache__/
m1.cpython-36.opt-1.pyc
m1.cpython-36.opt-2.pyc
m1.cpython-36.pyc

(3).pyc.py檔案都不會使程式執行得更快(不會提高執行速度)。但是,.pyc檔案能使載入速度更快,因為少了編譯的過程。

Python標準模組

Python附帶了一個標準模組庫。其中一些模組內建在直譯器中,它們提供對不屬於語言核心但仍然內建的操作的訪問,以提高效率或提供對系統呼叫等作業系統原語的訪問。這些模組的集合是一個配置選項,它也取決於底層平臺。例如,winreg模組僅在Windows系統上提供。一個值得注意的模組是sys,它內建於每個Python直譯器中。

Python的標註模組會在我們今後的程式設計中不斷遇到和使用,具體的學習可以在今後用到時再學習。你需要記住的一點是:當你寫Python程式碼需要某些基本功能時,一定要先找找看是否已經有標準模組存在,是否已經有人寫好了包含這些功能的模組,最後才要覺得自己要不要實現這些功能。

Python內建函式dir()

dir()用來檢視模組裡面定義的名字,包括:變數名,模組名,函式名等等。
它返回一個list:

In [5]: import my_first_module

In [6]: dir(my_first_module)
Out[6]: 
['MY_NAME',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'my_add',
 'my_print']

如果呼叫dir()不傳遞引數,則列出當前已經定義的所有名字:

In [1]: import my_first_module

In [2]: dir()
Out[2]: 
['In',
 'Out',
 '_',
 '__',
 '___',
 '__builtin__',
 '__builtins__',
 '__doc__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_dh',
 '_i',
 '_i1',
 '_i2',
 '_ih',
 '_ii',
 '_iii',
 '_oh',
 'exit',
 'get_ipython',
 'my_first_module',
 'quit']

用dir()可以檢視所有的內建型別、變數、函式等,方法是藉助標準模組builtins

>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

Python模組高階技巧總結

(1)Python的搜尋路徑,順序一定要搞得清;
(2)編譯後的.pyc檔案;
(3)dir()內建函式檢視模組定義的名字。

猿人學banner宣傳圖

我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。

***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***

相關文章