詳解Python中的import的用法

文章轉自我的知乎主頁(https://www。zhihu。com/people/wei-lai-22-71-5/posts),在知乎上,獲得了關於python的import用法討論的最高的贊,拿到頭條上分享給大家,希望對學習python的網友們能夠有所幫助。

Python用了快兩年了吧,其中有些東西一直是稀裡糊塗地用,import便是我一直沒有明白的東西。曾經有過三次解決它的機會,我都因得過且過、一拖再拖而沒能化敵為友。今天下午,它又給了我一次機會,我想我還是從了它的心願吧。

故事是從這篇臺灣同胞的部落格《Python的import陷阱》[1](網址見底部)開始的,然後又跳到了Python社群的PEP 328提案[2],再結合過去的經驗以及一些測試,我想我大概懂了吧。下面是我的總結,希望內容能夠言簡意賅、易於理解。

import語句有什麼用?import語句用來匯入其他python檔案(稱為模組module),使用該模組裡定義的類、方法或者變數,從而達到程式碼複用的目的。為了方便說明,我們用例項來說明import的用法,讀者朋友可以跟著嘗試(嘗試時建議使用python3,python2和python3在import的表現有差異,之後會提到)。

首先,先建立一個資料夾Tree作為工作目錄,並在其內建立兩個檔案m1。py和m2。py,在m1。py寫入程式碼:

import osimport m2m2。printSelf()

在m2。py寫入程式碼:

def printSelf(): print(‘In m2’)

開啟命令列,進入到Tree目錄下,敲下python m1。py執行,發現沒有報錯,且打印出In m2,說明這樣使用import沒有問題。由此我們總結出import語句的第一種用法。

import module_name。即import後直接接模組名。在這種情況下,Python會在兩個地方尋找這個模組,第一是sys。path(透過執行程式碼import sys; print(sys。path)檢視),os這個模組所在的目錄就在列表sys。path中,一般安裝的Python庫的目錄都可以在sys。path中找到(前提是要將Python的安裝目錄新增到電腦的環境變數),所以對於安裝好的庫,我們直接import即可。第二個地方就是執行檔案(這裡是m1。py)所在的目錄,因為m2。py和執行檔案在同一目錄下,所以上述寫法沒有問題。

用上述方法匯入原有的sys。path中的庫沒有問題。但是,最好不要用上述方法匯入同目錄下的檔案!因為這可能會出錯。演示這個錯誤需要用到import語句的第二種寫法,所以先來學一學import的第二種寫法。在Tree目錄下新建一個目錄Branch,在Branch中新建檔案m3。py,m3。py的內容如下:

def printSelf(): print(‘In m3’)

如何在m1中匯入m3。py呢,請看更改後的m1。py:

from Branch import m3m3。printSelf()

總結import語句的第二種用法:

from package_name import module_name。一般把模組組成的集合稱為包(package)。與第一種寫法類似,Python會在sys。path和執行檔案目錄這兩個地方尋找包,然後匯入包中名為module_name的模組。

現在我們來說明為什麼不要用import的第一種寫法來匯入同目錄下的檔案。在Branch目錄下新建m4。py檔案,m4。py的內容如下:

def printSelf(): print(‘In m4’)

然後我們在m3。py中直接匯入m4,m3。py變為:

import m4def printSelf(): print(‘In m3’)

這時候執行m1。py就會報錯了,說沒法匯入m4模組。為什麼呢?我們來看一下匯入流程:m1使用from Branch import m3匯入m3,然後在m3。py中用import m4匯入m4。看出問題了嗎?m4。py和m1。py不在同一目錄,怎麼能直接使用import m4匯入m4呢。(讀者可以試試直接在Tree目錄下新建另一個m4。py檔案,你會發現再執行m1。py就不會出錯了,只不過匯入的是第二個m4。py了)

面對上面的錯誤,使用python2執行m1。py就不會報錯,因為在python2中,上面提到的import的兩種寫法都屬於相對匯入,而在python3中,卻屬於絕對匯入。話說到了這裡,就要牽扯到import中最關鍵的部分了——相對匯入和絕對匯入。

我們還是談論python3的import用法。上面提到的兩種寫法屬於絕對匯入,即用於匯入sys。path中的包和執行檔案所在目錄下的包。對於sys。path中的包,這種寫法毫無問題;匯入自己寫的檔案,如果是非執行入口檔案(上面的m1。py是執行入口檔案,可以使用絕對匯入),則需要相對匯入。

比如對於非執行入口檔案m3。py,其匯入m4。py需要使用相對匯入:

from 。 import m4def printSelf(): print(‘In m3’)

這時候再執行m1。py就ok了。列舉一下相對匯入的寫法:

from 。 import module_name。匯入和自己同目錄下的模組。

from 。package_name import module_name。匯入和自己同目錄的包的模組。

from 。。 import module_name。匯入上級目錄的模組。

from 。。package_name import module_name。匯入位於上級目錄下的包的模組。

當然還可以有更多的。,每多一個點就多往上一層目錄。

不知道你有沒有留神上面的一句話——“上面的m1。py是執行入口檔案,可以使用絕對匯入”,這句話是沒問題的,也和我平時的做法一致。那麼,執行入口檔案可不可以使用相對匯入呢?比如m1。py內容改成:

from 。Branch import m3m3。printSelf()

答案是可以,但不能用python m1。py命令,而是需要進入到Tree所在的目錄,使用python -m Tree。m1來執行。為什麼?關於前者,PEP 328提案中的一段文字好像給出了原因:

Relative imports use a module‘s _name

_

attribute to determine that module’s position in the package hierarchy。 If the module‘s name does not contain any package information (e。g。 it is set to ’__main

__

‘) then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system。

我不太懂,但是又有一點明白。我們應該見過下面一段程式碼:

if __name__ == ’__main__‘: main()

意思是如果運行了當前檔案,則__name__變數會置為__main__,然後會執行main函式,如果當前檔案是被其他檔案作為模組匯入的話,則__name__為模組名,不等於__main__,就不會執行main函式。比如對於上述更改後的m1。py,執行python m1。py命令後,會報如下錯誤:

Traceback (most recent call last): File “m1。py”, line 1, in from 。Branch import m3 ModuleNotFoundError: No module named ’_main_。Branch‘; ’__main__‘ is not a package

據此我猜測執行python m1。py命令後,當前目錄所代表的包’。‘變成了__main__。

那為什麼python -m Tree。m1就可以呢?那位臺灣老師給出瞭解釋:

執行指令中的-m是為了讓Python預先import你要的package或module給你,然後再執行script。

即不把m1。py當作執行入口檔案,而是也把它當作被匯入的模組,這就和非執行入口檔案有一樣的表現了。

注意,在Tree目錄下執行python -m m1是不可以的,會報 ImportError: attempted relative import with no known parent package的錯誤。因為m1。py中的from 。Branch import m3中的。 ,直譯器並不知道是哪一個package。使用python -m Tree。m1,直譯器就知道。對應的是Tree這個package。

那反過來,如果m1。py使用絕對匯入(from Branch import m3),能使用python -m m1執行嗎?我試了一下,如果當前目錄是Tree就可以。如果在其他目錄下執行,比如在Tree所在的目錄(使用python -m Tree。m1執行),就不可以。這可能還是與絕對匯入相關。

(之前看到了一個大型專案,其執行入口檔案有一大堆的相對匯入,我還傻乎乎地用python直接執行它。之後看到他給的樣例執行命令是帶了-m引數的。現在才恍然大悟。)

理解import的難點差不多就這樣了。下面說一說import的其他簡單但實用的用法。

import moudle_name as alias。有些module_name比較長,之後寫它時較為麻煩,或者module_name會出現名字衝突,可以用as來給它改名,如import numpy as np。

from module_name import function_name, variable_name, class_name。上面匯入的都是整個模組,有時候我們只想使用模組中的某些函式、某些變數、某些類,用這種寫法就可以了。使用逗號可以匯入模組中的多個元素。

有時候匯入的元素很多,可以使用反斜槓來換行,官方推薦使用括號。

from Tkinter import Tk, Frame, Button, Entry, Canvas, Text, \ LEFT, DISABLED, NORMAL, RIDGE, END # 反斜槓換行from Tkinter import (Tk, Frame, Button, Entry, Canvas, Text, LEFT, DISABLED, NORMAL, RIDGE, END) # 括號換行(推薦)

說到這感覺import的核心已經說完了。再跟著上面的部落格說一說使用import可能碰到的問題吧。

問題1描述:ValueError: attempted relative import beyond top-level package。直面問題的第一步是去了解熟悉它,最好是能復現它,讓它躺在兩跨之間任我們去踐踏蹂躪。仍然是上面四個檔案,稍作修改,四個檔案如下:

# m1。pyfrom Branch import m3m3。printSelf()# m2。pydef printSelf(): print(’module2‘)# m3。pyfrom 。。 import m2 # 復現的關鍵在這 #print(__name__)def printSelf(): print(’In m3‘)# m4。pydef printSelf(): print(’In m4‘)

執行python m1。py,就會出現該問題。問題何在?我猜測,執行m1。py後,m1代表的模組就是頂層模組(參見上面PEP 328的引用),而m3。py中嘗試匯入的m2模組所在的包(即Tree目錄代表的包)比m1的層級更高,所以會報出這樣的錯誤。怎麼解決呢?將m1。py的所有匯入改為相對匯入,然後進入m1。py的上層目錄,執行python -m Tree。m1即可。

對於使用import出現的其他問題,碰到了再接著更新。

外部資料網址:

[1] https://link。zhihu。com/?target=https%3A//medium。com/pyladies-taiwan/python-%25E7%259A%2584-import-%25E9%2599%25B7%25E9%2598%25B1-3538e74f57e3

[2] https://link。zhihu。com/?target=https%3A//www。python。org/dev/peps/pep-0328/%23id1