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