1. 程式人生 > >Java程式猿學Python之Import語句

Java程式猿學Python之Import語句

Import語句可以說在Python中是無處不在,非常類似於Java中的import的語句,但是又比Java的強大很多,也複雜的多。

首先本文章會講解import語句的作用,然後講解一下import語句的內部執行原理以及import語句是按什麼樣的順序尋找檔案;再者講解from語句與import語句的使用,動態載入,包匯入(又分為相對匯入和絕對匯入),名稱空間包等與import相關的話題。本文章主要是我讀《Learning Python FIFTH EDITION》之後整理出來的,自己也能做一個總結和分享,歡迎各位踴躍提問。

Import語句的作用

作用跟java中的import語句基本一致,將其他檔案中編寫的模組匯入到當前檔案以供使用。這對於實際的系統開發非常有用,可以將系統按照相應的功能拆分到不同的檔案中,也可以將通用的功能獨立出來以供複用。

>>> import os
>>> os.getcwd()
'D:\\Program\\Python35'
>>> 

Import語句內部執行機制

import語句在第一次執行的時候主要執行如下幾個步驟:
1. 找到模組檔案;
檔案的定位過程及優先順序如下:
(1)程式的工作目錄 (自動匯入,不需要手動新增。)
(2)PYTHONPATH環境變數配置的檔案路徑;
(3)Python安裝包裡面的標準庫目錄; (自動匯入,不需要手動新增。)
(4)以上路徑中指定的.pth檔案 (該檔案每一行為路徑,會根據每行配置的路徑進行搜尋。)
(5)第三方擴充套件包; (自動匯入)
這些搜尋路徑可以通過如下命令查詢:

>>> sys.path
['', 'D:\\Program\\Python35\\Lib\\idlelib', 'D:\\Program\\Python35\\python35.zip', 'D:\\Program\\Python35\\DLLs', 'D:\\Program\\Python35\\lib', 'D:\\Program\\Python35', 'F:\\workspace\\python_workspace', 'D:\\Program\\Python35\\lib\\site-packages']
>>> 

其中的空目錄為程式執行的當前目錄,該目錄的優先順序最高,從左到右順序掃描目錄。

2. 編譯成位元組碼;
在IDLE中第一次匯入的時候會自動編譯成位元組碼檔案pyc,並快取到檔案所處的__pycache__目錄中(Python 3.X)。在正是的專案中一般都會全部編譯好,所以不會再進行編譯。

3. 執行模組程式碼;
首次匯入會執行處於模組頂層的程式碼:
script1.py:

X = 1
print(X)

def F():
    print('function')
>>> import script1
1
>>> 

注意,當第二次執行相同模組的匯入的時候不會再執行這麼多步驟,而是直接從快取中獲取模組物件而已。

From語句

from module1 import printer
從module1模組中只匯入printer

from module1 import printer, pxd
從模組module1中匯入printer和pxd
module1.py :

pxd = 10

def printer(X):
    print(X)
>>> from module1 import pxd, printer
>>> pxd
10
>>> printer(2)
2
>>> 

from module1 import * (只能使用在模組的頂級作用域,也就是模組的最外面。)
這樣可以直接使用printer屬性,而不需要模組名稱。
例如:

>>> from script1 import *
>>> def a():
    from script1 import *

SyntaxError: import * only allowed at module level
>>> 

該語法只能在模組的最外層執行,而不能在方法內。謹慎使用*,因為這樣會覆蓋本地作用域同名的屬性。

from M import func as mfunc
可以將匯入的屬性進行重新命名,當有命名衝突的時候as語句非常好用。

注意點:當使用from匯入進來的屬性進行修改時,不可變物件的修改不會影響到模組中的屬性。

module1.py

pxd = 10

def printer():
    print(pxd)
>>> import module1
>>> from module1 import pxd, printer
>>> pxd
10
>>> pxd = 11
>>> printer()
10
>>> module1.pxd
10
>>> 

單獨的修改import進來的pxd並沒有影響到module1模組中的屬性,因為pxd是不可變物件,import進來只是複製了一份值,但是如果是可變物件則是引用,這時修改就會影響到module1模組中的屬性:

module1.py :

pxd = 10
L = [1,2,4]

def printer():
    print(pxd)
>>> from module1 import L
>>> import module1
>>> L
[1, 2, 4]
>>> L.append(99)
>>> L
[1, 2, 4, 99]
>>> module1.L
[1, 2, 4, 99]
>>> 

因此,from語句的匯入可以看成這樣:

from module import name1, name2     
等價於:
import module               # Fetch the module object
name1 = module.name1        # Copy names out by assignment
name2 = module.name2
del module                  # Get rid of the module name

包匯入

包匯入就是按照資料夾的組織架構匯入模組,Java中的import就是按照這種方式匯入模組的。
首先來看一個例子,有一個模組檔案的目錄結構為:/dir0/dir1/dir2/mod.py,則程式碼:
import dir1.dir2.mod
匯入mod模組

from dir1.dir2.mod import x
匯入mod模組的x屬性

這裡的匯入格式就是包匯入,但是有如下要求:
1、根目錄/dir0必須在python的搜尋路徑下,也就是sys.path必須存在該目錄,但是sys.path中不能存在dir0目錄的子目錄(dir1, dir2等);
2、dir1和dir2必須要有__init__.py檔案;
__init__.py檔案就是一個普通的python程式檔案,__init__.py檔案用於在第一次import package的時候執行一些初始化動作,也相當於將目錄宣告成包目錄。當匯入了目錄之後,__init__.py物件相當於匯入的目錄物件。

這裡F:\workspace\python_workspace\處於搜尋路徑下。
F:\workspace\python_workspace\dir1\__init__.py

A = 1
print('dir1')

F:\workspace\python_workspace\dir1\dir2\__init__.py

B = 2
print('dir2')

F:\workspace\python_workspace\dir1\dir2\mod.py

C = 3
print('in mod.py')
>>> import dir1.dir2.mod
dir1
dir2
in mod.py
>>> dir1.A
1
>>> dir2.B
Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    dir2.B
NameError: name 'dir2' is not defined
>>> dir1.dir2.B
2
>>> dir1.dir2.mod.C
3
>>> dir1
<module 'dir1' from 'F:\\workspace\\python_workspace\\dir1\\__init__.py'>
>>> 

通過執行這段程式碼可以看出,__init__.py代表了這個目錄物件,包匯入必須通過dir1才能訪問到dir2 (dir1.dir2,當然也可以通過from語句直接訪問:from dir1 import dir2),這個跟目錄的結構一致。

通過以上可知,包匯入有2個好處:

  1. 可以合理的規劃模組,例如:org.spring.XX , org.mybatis.XX, org.hibernate.XX 等等,這樣的話可以清楚的知道模組的用途,表述性更強;
  2. 可以避免模組命名衝突,如果沒有包匯入,在Python搜尋路徑如果存在同名模組將非常麻煩。

相對匯入

在相對匯入中通過使用.和..開頭來指定當前目錄和上一層目錄,格式:
from . import m
from .. import n
要想使用相對匯入,所在的py檔案必須處在包路徑裡面,就是所在的資料夾下存在__init__.py檔案,並且該目錄的根目錄必須處於Python的搜尋路徑下; 就相當如上一節介紹的/dir0/dir1/dir2目錄結構,其中/dir0處於python的搜尋路徑下,而dir1和dir2不在python的搜尋路徑下,並且dir1和dir2都包含__init__.py檔案;是否處於python的搜尋路徑可以通過sys.path檢視。

F:\workspace\python_workspace\dir1\a.py

from .b import name         #包的相對匯入:在當前目錄下匯入b模組的name屬性
print('name:' + name)

F:\workspace\python_workspace\dir1\b.py

from . import a             #包的相對匯入:在當前目錄下匯入a模組
print('b.')
name = 'b:pengxd'

其中路徑F:\workspace\python_workspace\處於搜尋路徑下:

>>> import sys
>>> sys.path
['', 'D:\\Program\\Python35\\Lib\\idlelib', 'D:\\Program\\Python35\\python35.zip', 'D:\\Program\\Python35\\DLLs', 'D:\\Program\\Python35\\lib', 'D:\\Program\\Python35', 'F:\\workspace\\python_workspace', 'D:\\Program\\Python35\\lib\\site-packages']
>>> import dir1.a
b.
name:b:pengxd
>>> 

這裡有一個問題需要注意,不能使用python a.py執行含有相對匯入的模組,因為用python執行的時候當前目錄會變成python的搜尋目錄,這樣當前目錄就不是包路徑,無法使用包的相對匯入。

PS F:\workspace\python_workspace\dir1> python .\a.py
Traceback (most recent call last):
  File ".\a.py", line 1, in <module>
    from .b import name
ImportError: attempted relative import with no known parent package

名稱空間包 (Python 3.X)

在全域性的搜尋路徑下存在多個相同名稱的資料夾(不需要__init__.py檔案),可以使用import語句將這些資料夾掛載到一個名稱空間包下,例如,有2個檔案:
F:\workspace\python_workspace\ns\dir1\sub\mod1.py
F:\workspace\python_workspace\ns\dir2\sub\mod2.py
其中F:\workspace\python_workspace\ns\dir1\ 和 F:\workspace\python_workspace\ns\dir2\ 處於全域性的搜尋路徑下,當import sub的時候就會建立名稱空間包,程式碼如下:
這裡寫圖片描述

這樣sub模組下面就存在2個子模組:mod1,mod2。此時,sub為名稱空間包module ‘sub’ (namespace),它的__path__屬性有所包含的路徑。這樣就可以通過sub模組訪問到mod1和mod2模組了,這2個模組就像是在同一個目錄下面一樣。

注意:在名稱空間包下可以使用相對匯入,例如可以在mod1檔案中使用語句: from . import mod2

名稱空間包中不要包含__init__.py檔案,這樣會打亂名稱空間包的搜尋路徑,導致名稱空間包無法正常工作。例如:
F:\workspace\python_workspace\ns4\dir1\sub\mod1.py
F:\workspace\python_workspace\ns4\dir2\sub\mod2.py
如果dir2/sub/下存在一個__init__.py檔案,那麼在匯入sub模組的時候會優先使用普通包,也就是dir2下的sub模組。
這裡寫圖片描述
這個時候sub模組只是一個普通包(__init__.py)

import語句的封裝

1、在使用import * 的時候預設不會匯入已下劃線開頭的變數;
unders.py:

print('init...')
__a__ = 10
b = 20
>>> from unders import *
init...
>>> b
20
>>> __a__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '__a__' is not defined
>>>

這裡可以看出並沒有匯入 __a__變數。

2、如果匯入的模組中存在變數__all__,該變數為一個list列表,其中每一個元素為import * 預設匯入的內容。
例如模組unders.py:

print('init...')
__a__ = 10
c = 30
b = 20      #該變數不在__all__列表中,因此不會被import * 自動匯入
d = 40
__all__ = ['__a__', 'c', 'd']   #其中的變數會被import * 自動匯入
>>> from unders import *
init...
>>> __a__
10
>>> c
30
>>> b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
>>> d
40
>>>

使用import語句體驗新功能

from __future__ import featurename
使用該語句可以把新版本的內容匯入到當前版本中使用,因此使用__future__過渡到新版本會是一個很好的辦法。
例如,在Python 2.X中使用Python 3.X的除法功能:

from __future__ import division

print '10 / 3 =', 10 / 3
print '10.0 / 3 =', 10.0 / 3
print '10 // 3 =', 10 // 3

模組屬性:__name__ 和 __main__

如果最高級別的模組(也就是main函式模組),則屬性__name__ = ‘__main__’, 類似於java中的main函式一樣。
若果是匯入的模組,則 __name__ 屬性為import為模組名稱。
這2個屬性用於判斷當前模組是否為main模組然後執行相應的特殊操作非常有用。

Import重新命名

import modulename as name
from modulename import attrname as name
import dir1.dir2.mod as mod
可以使用as語句將模組重新命名

動態匯入

執行時動態的載入模組

>>> modname = 'string'
>>> string = __import__(modname)
>>> string
<module 'string' from 'C:\\Python33\\lib\\string.py'>

>>> import importlib
>>> modname = 'string'
>>> string = importlib.import_module(modname)   
>>> string
<module 'string' from 'C:\\Python33\\lib\\string.py'>

重新匯入修改後的模組

我們知道,一個模組只能匯入一次,如果再次匯入則直接從快取中直接獲取,並不會重新匯入模組。如果想在修改模組之後再次匯入,可以使用reload方法:

>>> from imp  #方法1
>>> imp.reload(module)

>>> from imp import reload #方法2
>>> reload(module)

注意:
1、只有Python語言編寫的模組才可以動態的載入,而第三方擴充套件模組,如用C編寫的則不能動態載入。
2、使用reload之前必須先用import把相應的模組匯入進行才能執行reload方法。
3、reload隻影響import語句匯入進來的模組,而不響應from語句匯入的模組,因此如果想重新匯入模組請不要使用from語句。

總結

Python的Import語句就介紹到這裡,可以看出其複雜性還是很多的,要注意的點也非常多,不像Java中的import語句這麼簡單,但也豐富了Python的功能。在後續的學習過程中我也會總結這裡經驗,也會不斷的完善和總結,爭取能將最準確的資訊傳遞給大家,也非常希望大家的反饋,大家可以一起學習交流。