可能是史上最細(xì)的python中import詳解
以前在使用import的時候經(jīng)常會因為模塊的導(dǎo)入而出現(xiàn)一些問題,以及一些似懂非懂半疑惑半糊涂的問題,索性花了點時間研究了一些python引用的方法,并且動手操作試驗了一下,深有感觸,特留此文以作總結(jié),如有不當(dāng)之處歡迎評論指正
本文會盡我所能詳細(xì)描述,字?jǐn)?shù)會比較多,希望各位耐心看完。
首先我覺得應(yīng)該先了解一下python的引用是怎么引用的
我們首先新建一個python文件demo.py
print(dir())
dir()命令是獲取到一個object的所有屬性,當(dāng)傳值為空時默認(rèn)傳入的是當(dāng)前py文件,我們可以通過這個函數(shù)來查看py文件的所有屬性
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
雙下劃線開頭結(jié)尾的變量是python中一類特殊的變量,稱為python的魔法函數(shù),這里簡單解釋一些常見屬性的意義
- __annotations__:預(yù)定義的變量類型
- __doc__:文檔注釋,僅包括第一個''' '''內(nèi)部的注釋文檔
- __file__:文件名,根據(jù)被引用還是被執(zhí)行返回絕對路徑還是相對路徑
- __name_:文件名,被執(zhí)行文件該屬性為__main__,被引用文件則返回包名
當(dāng)我們在python文件中寫入一些代碼后
a = 1 class b: def __init__(self) -> None: self.value = "123" c = [1,2,3] def f(a,b): return a+b print(dir())
再次執(zhí)行后可以發(fā)現(xiàn)
['__annotations__', '__builtins__', '__cached__', '__doc__',
'__file__', '__loader__', '__name__', '__package__', '__spec__',
'a', 'b', 'c', 'f']
相較于之前,所有的變量、類、函數(shù)都被加入py文件的屬性值中了,也正是如此才能在之后使用這些已經(jīng)聲明的變量或者函數(shù)。
對于以下比較常見的庫文件引入方式
import math import torch.nn as nn from numpy import arange print(dir())
同理我們可以比較清晰的看到,當(dāng)引入了一個python庫文件的時候其實也就是在python文件中加入了這個庫名字,如果是用 as 關(guān)鍵字進(jìn)行重命名在文件中也會以重命名之后的名字來做保留
['__annotations__', '__builtins__', '__cached__', '__doc__',
'__file__', '__loader__', '__name__', '__package__', '__spec__',
'math','nn','arange']
這時我突然產(chǎn)生了一個疑問,那我們使用的print,dir這兩個函數(shù)又是在哪里定義的呢,我似乎并沒有引用任何的庫就直接使用了呢。
這其實是一個相當(dāng)有趣的問題,不過我們先稍等一下,在后面我會回答這個問題。
我們再來帶入一個實際的場景,通常一個較為復(fù)雜的項目是多文件協(xié)同工作的,其中不乏為了命名空間的統(tǒng)一一致性,為了整體文件結(jié)構(gòu)的清晰有序等多種目的而使用多級目錄來合理劃分文件關(guān)系,梳理整體代碼架構(gòu)。
我們引入如下的文件系統(tǒng)環(huán)境(此示意圖會在文中適當(dāng)?shù)奈恢弥貜?fù)出現(xiàn)以加強(qiáng)記憶)
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
并且每一個a/b/c/d.py文件中分別定義了f1-f4函數(shù)以供調(diào)用,示意如下:
#a.py def f1(): print("this is function f1 in a.py")
然后我們在demo.py中執(zhí)行以下代碼,很顯然正確引入沒有問題
#demo.py from folder1.a import f1 from folder1.b import f2 from folder2.c import f3 from folder2.d import f4 f1() f2() f3() f4() # 輸出: # this is function f1 in a.py # this is function f2 in b.py # this is function f3 in c.py # this is function f4 in d.py
如果我在a.py中想使用b.py中的f2,我也可以更改并執(zhí)行,沒有任何問題
from b import f2 def f1(): print("this is function f1 in a.py") f2() # 輸出: # this is function f2 in b.py
但如果我想在a.py中使用c.py中的f3顯然需要一些別的手段,因為這涉及到了跨文件夾的引用
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
考慮到文件結(jié)構(gòu)層次,a.py位于目錄folder1下,我們希望a.py能夠回到上一級目錄python下,這樣就能再進(jìn)入folder2/c.py順利引用了。
很多文件也都是這樣做的,加入了一個import sys,sys.path,sys.path.append(".")然后問題似乎就順利解決了,
import sys sys.path.append(".") from folder2.c import f3 def f1(): print("this is function f1 in a.py") f3() # 輸出: # this is function f3 in c.py
不過這種做法為什么可行呢,我們不妨來探究一下這種做法正確執(zhí)行背后的邏輯
首先我們了解一下sys.path有什么作用,我們在demo.py中執(zhí)行
import sys print(sys.path)
這里我使用的python環(huán)境是Anaconda3創(chuàng)建的一個python3.7虛擬環(huán)境,環(huán)境名稱為fastreid
['g:\\learner_lu\\code-grammar\\python',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid',
'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib\\site-packages']
我們可以觀察到sys.path中包含了許多絕對路徑,第一個路徑似乎是demo.py的所在的文件夾,其他的路徑配置過環(huán)境變量的的小伙伴想必會覺得很眼熟。
而如果我們選擇執(zhí)行a.py,我們會得到以下結(jié)果:
['g:\\learner_lu\\code-grammar\\python\\folder1',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid',
'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib\\site-packages']
唯一的區(qū)別就是第一個,也印證了我們的猜想,sys.path中的第一個值是被執(zhí)行的py文件所在文件夾在操作系統(tǒng)中的絕對路徑。
那么現(xiàn)在問題來了,其余的路徑是什么呢?
- C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip是python的壓縮包,解壓之后就會被刪除,路徑無效
- C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs中是所有的.pyd格式的文件,是一種D語言的加密格式,該格式可以被引用但是不能被查看源代碼。
- C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib中是python自帶的一些庫,隨python一起安裝,可以看到我們常見的一些copy.py,glob.py,io.py,os.py
- C:\\ProgramData\\Anaconda3\\envs\\fastreid是python解釋器python.exe所在的目錄 ,也是整個py文件被執(zhí)行時需要啟動用來逐行解釋語句的文件
- 剩下兩個site-packages則分別是使用pip install / conda install時包的安裝位置, 相信對于使用python的小伙伴們來說下載第三方庫的操作并不陌生
好了,回到剛才的問題,sys.path中是 被執(zhí)行的py文件所在文件夾在操作系統(tǒng)中的絕對路徑以及使用的python環(huán)境下所有的庫的目錄
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
到這里已經(jīng)解決了我的一些疑惑
為什么能 a.py 中能import b ?原來是g:\\learner_lu\\code-grammar\\python\\folder1目錄下能找到這個文件
為什么能import os?原來是 C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib目錄下有os.py
為什么能import numpy? 原來是放在xxx\\site-packages下了
所以我們可以總結(jié)一下,只要是在sys.path中的路徑下能被找到的文件,我們就可以直接使用import引用。
那么之前的問題就很好解釋了,a.py的sys.path下找不到c.py,所以我們需要在sys.path中加入能找到c.py的路徑,有以下兩種方法
#method-1 import sys sys.path.append('g:\\learner_lu\\code-grammar\\python\\folder2') import c c.f3() # 輸出: # this is function f3 in c.py #method-2 import sys sys.path.append('g:\\learner_lu\\code-grammar\\python') import folder2.c folder2.c.f3() # 輸出: # this is function f3 in c.py
雖然可以執(zhí)行不過顯然這種方式比較麻煩,因為你還需要輸入目錄的絕對路徑,這顯然不是一個簡便的做法。
那么一開始介紹時所使用的sys.path.append(".")又是什么意思呢 ?
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
.表示的是當(dāng)前目錄,也就是現(xiàn)在你所在的目錄。比如我現(xiàn)在處于G:\learner_lu\code-grammar\python,我想要執(zhí)行a.py可以通過相對路徑訪問,python folder1/a.py, 而sys.path.append(".") 就是加入了當(dāng)前所在的G:\learner_lu\code-grammar\python路徑,與sys.path.append("G:\learner_lu\code-grammar\python")完全一致。
但如果我現(xiàn)在更進(jìn)一步,我現(xiàn)在處于G:\learner_lu\code-grammar\python\folder1,那么我想直接運行a.py則可以直接python a.py,這時候的sys.path.append(".")相當(dāng)于sys.path.append("G:\learner_lu\code-grammar\python\folder1")
也就是說,sys.path.append(".")其實就是在sys.path中加入當(dāng)前所在的目錄的絕對路徑,會隨著你cd進(jìn)入或者退出到其他目錄而發(fā)生改變。
這確實是一種解決辦法,省去了繁瑣的絕對路徑的輸入而把當(dāng)前目錄(通常是整個項目最外層的根目錄)加入,這樣可以直接在文件中引入任意文件的位置。這種做法在許多項目中也都有應(yīng)用。
#當(dāng)前目錄:G:\learner_lu\code-grammar\python import sys sys.path.append('.') from folder2.c import f3 f3() # 輸出: # this is function f3 in c.py
使用這種做法時一定要注意你所在的目錄最好是根目錄,這樣能引用到所有文件,如果不在也可以通過sys.path.append('..')等方式得到一個更大的引用范圍
還有一種引用的用法也十分常見,就是文件之間通過相對引用.,..,....等文件、函數(shù)相互引用,比如還是之前的文件結(jié)構(gòu)
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
我重寫了a.py的f1函數(shù),它調(diào)用了來自b.py的f2以及c.py中的f3, 然后我想在demo.py中使用f1函數(shù),這種情況比較常見,那我們應(yīng)該怎么做呢?
首先我改寫a.py,采用相對引用導(dǎo)入f2,f3
from .b import f2 from ..folder2.c import f3 def f1(): print("start f1") f2() f3() print("end f1")
其次我改寫demo.py
from folder1.a import f1 f1()
看起來合情合理,但是運行demo.py時卻報錯了。
Traceback (most recent call last):
File "g:\learner_lu\code-grammar\python\demo.py", line 3, in <module>
from folder1.a import f1
File "g:\learner_lu\code-grammar\python\folder1\a.py", line 4, in <module>
from ..folder2.c import f3
ValueError: attempted relative import beyond top-level package
報錯原因是“在頂級包之外進(jìn)行相對引用”,說的云里霧里沒太明白什么意思。
那我們運行a.py來先試試看a.py有沒有問題?
from .b import f2 from ..folder2.c import f3 def f1(): print("start f1") f2() f3() print("end f1") f2() f3()
這時候更奇怪的事情發(fā)生了,報錯的位置甚至提前了,第一次至少from .b import f2沒有報錯,現(xiàn)在居然也報錯了?
Traceback (most recent call last):
File "g:\learner_lu\code-grammar\python\folder1\a.py", line 3, in <module>
from .b import f2
ImportError: attempted relative import with no known parent package
報錯原因是“嘗試在沒有已知父包的情況下進(jìn)行相對導(dǎo)入”,明明我用from b import f2來導(dǎo)入沒有問題呀,a.py和b.py同目錄我導(dǎo)入加一個同目錄的.又有什么問題呢 ?這些報錯又都是什么意思呢?
這時我們不妨先停一下,我們先來分析一下python是如何進(jìn)行相對引用的導(dǎo)入模塊的。
通過開頭的dir() 函數(shù)我們可以看到每一個py文件都有一個__name__屬性
- 當(dāng)這個文件被執(zhí)行時,這個屬性的值為"__main__ "
- 當(dāng)它作為模塊被引用時,它的屬性的值是當(dāng)前執(zhí)行的文件到它的相對路徑
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
還是原來的文件目錄,我們輸出不同情況下c.py的__name__屬性
#demo.py from folder2.c import f3 ------------------------------------------- #d.py from c import f3 ------------------------------------------- #c.py print(__name__) def f3(): print("this is function f3 in c.py") # 執(zhí)行demo.py # 輸出: # folder2.c # 執(zhí)行d.py # 輸出: # c # 執(zhí)行c.py # 輸出: # __main__
而相對引用的原理也就在此,根據(jù)模塊的__name__值和使用的.,..等相對路徑來搜索文件。
所以說回到剛才的問題,為什么a.py和b.py同目錄下使用from .b import f2的相對引用失敗了呢?
同目錄沒問題,相對引用使用.b也沒有問題,問題出現(xiàn)在當(dāng)你執(zhí)行a.py時,它的包名,也就是__name__ 的值是"__main__",無法通過“__main__"來找到其他文件。也就是說如果a.py并不直接運行,而只是以模塊的方式和b.py進(jìn)行相對引用是沒有問題的,例如:
#a.py from .b import f2 def f1(): print("this is function f1 in a.py") ------------------------------------------- #b.py def f2(): print("this is function f2 in b.py") ------------------------------------------- #demo.py from folder1.a import f1,f2 f1() f2() # 執(zhí)行demo.py # 輸出: # this is function f1 in a.py # this is function f2 in b.py
因為這是a.py現(xiàn)在沒有被執(zhí)行,它的的__name__屬性是被執(zhí)行的demo.py到a.py的相對路徑,也就是folder1.a,而我們執(zhí)行demo.py時首先加入sys.path中的就是 demo.py所在的文件夾,也就是G:\learner_lu\code-grammar\python,它使用.b進(jìn)行相對引用也就是先尋找一個folder1的文件夾,在其中再試圖找到一個b.py的文件,可以找到該文件,所以正確執(zhí)行
那如果我就是想通過相對引來引用b.py并且執(zhí)行a.py呢?那么依照原理,我們首先更改a的"__name__" 屬性,其次我們還需要能找到folder1這個文件夾,因為執(zhí)行a.py是在sys.path中加入的是 G:\learner_lu\code-grammar\python\folder1它無法找到自己的.,方法如下
#a.py __name__ = "folder1.a" import sys # 當(dāng)前目錄是/python sys.path.append(".") from .b import f2 def f1(): print("this is function f1 in a.py") f2()
正確執(zhí)行沒有問題,多說一句,其中__name__的值不一定非要是"folder1.a",因為反正是取a的同級目錄,那么a的包名是什么其實無關(guān)緊要,改為"folder1.asd","folder1.pql"等都可以,但是只能有一個.,多個會被認(rèn)為是多層的目錄結(jié)構(gòu),尋找的位置就錯誤了。
不過這種直接改寫name的方式圖一樂也就好了, 能直接引用完全沒必要畫蛇添足
所以回到剛才的第二個報錯信息,使用from .b import f2時報錯,“嘗試在沒有已知父包的情況下進(jìn)行相對導(dǎo)入”:
就是因為你是直接運行的a.py,它的包名__name__值是__main__,你又怎么能通過它來找相對引用的文件的位置呢? 而我們運行demo.py時這條引用沒有報錯是因為這時候a.py的__name__值為folder1.a,可以通過相對引用關(guān)系找到folder.b。
然后是剛才的另一個問題,我運行的是demo.py,a.py/b.py/c.py之間通過包名采用相互引用問題在哪里呢?看起來他們的__name__似乎沒什么問題啊?
from .b import f2 from ..folder2.c import f3 def f1(): print("start f1") f2() f3() print("end f1")
demo.py直接引用a沒有問題,問題在于a.py中from ..folder2.c import f3
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
運行 demo.py時a.py的包名為folder1.a,你試圖通過..來進(jìn)入其上級目錄,其所在目錄folder1的上級目錄是python,而執(zhí)行demo.py時首先加入sys.path的是G:\learner_lu\code-grammar\python目錄無法通過其找到同級目錄python,需要更高一級目錄引用。
所以其報錯信息:“在頂級包之外進(jìn)行相對引用”,就是說現(xiàn)在a.py的包名不夠我搜索到它的上級目錄,folder1.a僅僅夠我執(zhí)行.,如果你想執(zhí)行..那么你的包名至少要是python.folder1.a或者更長
有兩種解決方法:
1.提高demo.py的目錄等級,和python處于同一級下,即文件系統(tǒng)更改為
|—— demo.py
|—— python
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
在demo.py中使用from python.folder1.a import f1,提高demo.py位置相當(dāng)于擴(kuò)大a包的名字,由原來的folder1.a變?yōu)楝F(xiàn)在python.folder1.a,可以使用..來跳至python目錄搜索folder2
2.原目錄不變,更改a.py引用c.py的間接引用為直接引用
#a.py from .b import f2 from folder2.c import f3 def f1(): print("this is function f1 in a.py") f2() f3() print("end of f1")
demo.py不變
from folder1.a import f1 f1() # 輸出: # this is function f1 in a.py # this is function f2 in b.py # this is function f3 in c.py # end of f1
我在初學(xué)python的時候就被告訴說python的引用多級目錄之間就直接用.就可以了,這就是python的語法,并沒有思考它背后的原理。
其實我們使用.來連接目錄與目錄,目錄與文件,最后寫出來的import python.folder1.a其實就是這個文件的包名,python也就是通過查找包名來引入模塊的。
現(xiàn)在可以來回答一下開頭提出的問題,print函數(shù)在哪里被引用了呢?我似乎并沒有引入任何模塊就直接使用了呢。
python中有一些函數(shù)叫做內(nèi)置函數(shù),觀察開頭所提到的py文件的屬性,有一個屬性叫做'__builtins__',這個屬性包含了所有內(nèi)置函數(shù),我們可以通過print(dir(__builtins__))進(jìn)一步查看
['ArithmeticError', 'AssertionError', 'AttributeError'.......,
abs,any,chr,help,set,round,sum,tuple,list,zip,min,max,str....
print,next,object,pow,quit,dir,.....]
眾多我們或熟悉或陌生的內(nèi)置函數(shù),這些函數(shù)并不像其他的py文件需要被導(dǎo)入,python是用c語言來實現(xiàn)的,這些內(nèi)置函數(shù)也是。你可以在[這里](https://hg.python.org/cpython/file/937fa81500e2/Python/bltinmodule.c)找到所有內(nèi)置函數(shù)的代碼實現(xiàn),在[這里](https://hg.python.org/cpython/file/937fa81500e2/Python/bltinmodule.c#l1567)找到print函數(shù)的源代碼實現(xiàn),他們是用c語言完成的,他們引用的頭文件也都可以在C:\ProgramData\Anaconda3\envs\fastreid\include中到找到對應(yīng)的源代碼,例如code.h,eval.h,但是沒有對應(yīng)的c源文件,本身已經(jīng)被編譯鏈接成python.exe文件了。關(guān)于cpython和python的關(guān)系見文末。
當(dāng)我們使用一個函數(shù)時,python首先會查找它是否在該py文件中被引入了,如果沒有找到那么就會到內(nèi)置函數(shù)中去查找,再找不到就會報錯。
那如果對于模塊引用又是怎么查找的呢,優(yōu)先級是怎么樣的呢?
比如我們可以定義一個print函數(shù),他就會覆蓋內(nèi)置函數(shù)print被調(diào)用
def print(x): return print("123") #無輸出
現(xiàn)在我們在c.py的同目錄下創(chuàng)建一個copy.py文件,那我們引用import copy時引用的是我定義的copy.py還是python環(huán)境中的'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib'下的copy.py呢?
現(xiàn)在的目錄結(jié)構(gòu)如下:
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— copy.py
| |—— d.py
我在copy.py中定義一個函數(shù)deepcopy,它的作用僅僅輸出一句話
#copy.py def deepcopy(x): print(f"this is just a deepcopy function of {x} in copy.py")
而原'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib'中的copy.py中的同名函數(shù)deepcopy的作用是對變量進(jìn)行深拷貝,全新的復(fù)制了一份變量,他們具有不同的地址,相同的值。
現(xiàn)在我在c.py中import copy,那么它會引用哪個呢?
#c.py import copy copy.deepcopy("123") # 輸出: # this is just a deepcopy function of 123 in copy.py
可以看到引用了同目錄下的copy.py而不是python環(huán)境中的。
這似乎說明了引用的原則 : 優(yōu)先引用執(zhí)行文件的目錄下的文件。
這時我又產(chǎn)生了疑問,那如果我像之前一樣,把copy.py的目錄加入sys.path中,不在同目錄下引用也是一樣的么?
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— copy.py
| |—— d.py
我在demo.py中把folder2 的目錄路徑加入sys.path,直接使用import copy引用
#demo.py #當(dāng)前目錄/python import sys sys.path.append("./folder2") import copy copy.deepcopy("123") # 無輸出
很奇怪,居然沒有輸出,這說明它調(diào)用的是python環(huán)境中的copy.py對“123”進(jìn)行了深拷貝。
哦我發(fā)現(xiàn)了,我使用的是sys.path.append,在列表結(jié)尾加入,也就是說現(xiàn)在的sys.path是這樣的
['g:\\learner_lu\\code-grammar\\python',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid',
'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib\\site-packages',
'./folder2']
那我要是在列表的頭部插入呢?也就是把sys.path變?yōu)?/p>
['./folder2',
'g:\\learner_lu\\code-grammar\\python',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid',
'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',
'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib\\site-packages']
#demo.py #當(dāng)前目錄 /python import sys sys.path.insert(0,"./folder2") import copy copy.deepcopy("123") # 輸出 # this is just a deepcopy function of 123 in copy.py
它又引用了我們定義的copy.py了 !
似乎我們離真相更近了,它是按照sys.path的順序來搜索的,如果找到了就不會繼續(xù)往下搜索了,也就是返回第一個找到的結(jié)果
現(xiàn)在我們驗證一下猜想,我們再嘗試一個引用一個math
|—— python
| |—— demo.py
| |—— folder1
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— copy.py
| |—— d.py
| |—— math.py
新建文件math.py,定義變量pi="123",然后在c.py中引用!
#math.py pi = "123" ------------------------------- #c.py from math import pi print(pi) # 輸出: # 3.141592653589793
咦?為什么還是原先的值,不是在同目錄下我應(yīng)該被優(yōu)先引用么?為什么copy.py行你又犯病了?
經(jīng)過仔細(xì)檢查才發(fā)現(xiàn),原來math是一個內(nèi)置的庫,與內(nèi)置函數(shù)類似,也是由c語言編寫。
import sys print(sys.builtin_module_names) # 輸出: ('_abc', '_ast', '_bisect', '_blake2', '_codecs',.... 'math', 'mmap', 'msvcrt', 'nt', 'parser', 'sys', 'time'...)
我們使用的sys,time,math等模塊都是內(nèi)置庫,他們需要被顯式的通過import xxx引用,并且他們的引用的優(yōu)先級要高于所有的其他的模塊,也就是sys.path中文件只有在與內(nèi)置庫文件不同名是才會被引用,否則引用的一定是內(nèi)置庫模塊
現(xiàn)在引用的順序已經(jīng)呼之欲出了
- 首先檢查是否是內(nèi)置庫,即在sys.builtin_module_names中搜索,返回第一個找到的結(jié)果
- 其次順序在sys.path中搜索,排在前面的優(yōu)先被找到,返回第一個找到的結(jié)果
這就需要注意文件的命名規(guī)范以及函數(shù)作用域等等,除函數(shù)重載外盡量避免使用相同的命名,否則后引入的會覆蓋先引入的,例如:
#a.py def f(): print("this is function f in a.py") ---------------------------------------- #b.py def f(): print("this is function f in b.py") ---------------------------------------- #demo.py from folder1.a import f from folder1.b import f f() # 輸出: # this is function f in b.py
誰后引入使用哪個,這也警示我們在自己編寫代碼時一定要注意文件、函數(shù)、變量的命名規(guī)范,一旦錯引亂引重載覆蓋等都是不易察覺的。
我們又會發(fā)現(xiàn),在demo.py中一個一個引入似乎有些太麻煩了,如果demo2、demo3、demo4都需要引入那大段大段地引入,哪怕是復(fù)制粘貼顯然也不是我們想要的。能不能有方便一點的方式來引入模塊呢?
我們可以在folder1下新建一個__init__.py文件,目錄結(jié)構(gòu)如下:
|—— python
| |—— demo.py
| |—— folder1
| |—— __init__.py
| |—— a.py
| |—— b.py
| |—— folder2
| |—— c.py
| |—— d.py
如今python3中已不需要顯式的定義__init__.py文件來將某文件夾作為包來使用,不過我們可以在其中加入一些東西以方便我們使用,加入如下代碼
#__init__.py from .a import f1 from .b import f2
這樣我們可以直接在demo.py中引用
from folder1 import * f1() f2() # 輸出 # this is function f1 in a.py # this is function f2 in b.py
這里的__init__.py的作用就是在引用folder1 時會優(yōu)先執(zhí)行這個初始化文件,把folder1作為包來將其下所有的引用集合起來,這樣只需要from folder1 import *就可以引用所有的定義了。
除此之外還能在一些文件中遇到__all__這種變量,其實我們在使用from xxx import *的時候就是調(diào)用了__all__這個變量,它默認(rèn)包含所有子文件,屬性是一個字符串組成的列表。當(dāng)然我們可以顯式的定義這個變量以達(dá)到一些效果,比如:
|—— python
| |—— demo.py
| |—— folder1
| |—— __init__.py
| |—— a.py
| |—— b.py
-
我們在a.py下加入幾個函數(shù)
#a.py def f1(): print("this is function f1 in a.py") def fx(): print("this is function fx in a.py") def fy(): print("this is function fy in a.py")
使用dir()查看demo.py中引入的變量
#__init__.py from .a import * from .b import * #demo.py from folder1 import * print(dir()) # 輸出 # ['__annotations__', '__builtins__', '__cached__', '__doc__', # '__file__', '__loader__', '__name__', '__package__', '__spec__', # 'a', 'b', 'f1', 'f2', 'fx', 'fy']
可以看到__init__.py調(diào)用了a,b, 又繼續(xù)import * 調(diào)用a.py b.py中所有的函數(shù) ,所有函數(shù)都可以使用
但如果我們在a.py中加入__all__的限制
#a.py __all__ = ["f1","fy"] def f1(): print("this is function f1 in a.py") def fx(): print("this is function fx in a.py") def fy(): print("this is function fy in a.py") ------------------------------------------- #demo.py from folder1 import * print(dir()) # 輸出 # ['__annotations__', '__builtins__', '__cached__', '__doc__', # '__file__', '__loader__', '__name__', '__package__', '__spec__', # 'a', 'b', 'f1', 'f2', 'fy']
fx這個函數(shù)就會被拋棄,不會被引用。這種方式通常被用來剔除當(dāng)前文件中的一些私有基類以及基函數(shù),不希望被引用。
類似的我們也可以在__init__.py中使用
from .a import * from .b import * __all__ = ["fx","b"] #demo.py from folder1 import * print(dir()) # 輸出 # ['__annotations__', '__builtins__', '__cached__', '__doc__', # '__file__', '__loader__', '__name__', '__package__', '__spec__', # 'b', 'fx']
這樣在demo.py中只能使用約束在__all__中的函數(shù)或變量,可以手動區(qū)分命名空間,防止污染
需要注意的是,只有在使用from xxx import *這種引用方式時才涉及到__all__,其他的引用方式設(shè)置此變量都對引用無影響。
總結(jié)
到此這篇關(guān)于python的import詳解就介紹到這了,最后祝大家天天進(jìn)步!!,更多相關(guān)python中import詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python 隨機(jī)生成10位數(shù)密碼的實現(xiàn)代碼
這篇文章主要介紹了python 隨機(jī)生成10位數(shù)密碼的實現(xiàn)代碼,在文中給大家提到了生成隨機(jī)密碼要實現(xiàn)的功能,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-06-06一文帶你掌握Python中enumerate函數(shù)和for循環(huán)的對比
在Python編程中,循環(huán)是一項常見的任務(wù),而for循環(huán)是最常見的一種,然而,Python提供了enumerate函數(shù),它允許在迭代過程中訪問元素的同時獲得它們的索引,下面我們就來學(xué)習(xí)一下二者的區(qū)別吧2023-11-11python實現(xiàn)飛機(jī)大戰(zhàn)小游戲
這篇文章主要為大家詳細(xì)介紹了python實現(xiàn)飛機(jī)大戰(zhàn)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-11-11Python實現(xiàn)計算字符串中出現(xiàn)次數(shù)最多的字符示例
這篇文章主要介紹了Python實現(xiàn)計算字符串中出現(xiàn)次數(shù)最多的字符,涉及Python針對字符串的遍歷、統(tǒng)計等相關(guān)操作技巧,需要的朋友可以參考下2019-01-01django rest framework serializer返回時間自動格式化方法
這篇文章主要介紹了django rest framework serializer返回時間自動格式化方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03