import語句用來導入其他python文件(稱為模塊module),使用該模塊里定義的類、方法或者變數,從而達到代碼復用的目的。 將要建立文件的結構為: Tree |____ m1.py |____ m2.py |____ Branch |____m3.py |____m4.py 首先,先建立一個 ...
import語句用來導入其他python文件(稱為模塊module),使用該模塊里定義的類、方法或者變數,從而達到代碼復用的目的。
將要建立文件的結構為:
Tree
|____ m1.py
|____ m2.py
|____ Branch
|____m3.py
|____m4.py
首先,先建立一個文件夾Tree作為工作目錄,併在其內建立兩個文件m1.py和m2.py,在m1.py寫入代碼:
import os
import m2
m2.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 m3
m3.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 m4
def 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 m4
def 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 m3
m3.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.py
from Branch import m3
m3.printSelf()
# m2.py
def printSelf():
print('module2')
# m3.py
from .. import m2 # 復現的關鍵在這 #
print(__name__)
def printSelf():
print('In m3')
# m4.py
def 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即可。