欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

可能是史上最細(xì)的python中import詳解

 更新時間:2022年02月25日 09:57:12   作者:天天開心學(xué)編程  
import在python中的意思是用來調(diào)用模塊的,下面這篇文章主要給大家介紹了關(guān)于python中import詳解的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下

以前在使用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)代碼

    這篇文章主要介紹了python 隨機(jī)生成10位數(shù)密碼的實現(xiàn)代碼,在文中給大家提到了生成隨機(jī)密碼要實現(xiàn)的功能,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • Python接口測試get請求過程詳解

    Python接口測試get請求過程詳解

    這篇文章主要介紹了python接口測試 get請求過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • 一文帶你掌握Python中enumerate函數(shù)和for循環(huán)的對比

    一文帶你掌握Python中enumerate函數(shù)和for循環(huán)的對比

    在Python編程中,循環(huán)是一項常見的任務(wù),而for循環(huán)是最常見的一種,然而,Python提供了enumerate函數(shù),它允許在迭代過程中訪問元素的同時獲得它們的索引,下面我們就來學(xué)習(xí)一下二者的區(qū)別吧
    2023-11-11
  • 分享13個好用到起飛的Python技巧

    分享13個好用到起飛的Python技巧

    編程是有技巧的,能寫的出程序的人很多,但能寫的又快又好是有技巧的,下面這篇文章主要給大家介紹了13個好用到起飛的Python技巧,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2021-10-10
  • Python3視頻轉(zhuǎn)字符動畫的實例代碼

    Python3視頻轉(zhuǎn)字符動畫的實例代碼

    這篇文章主要介紹了Python3視頻轉(zhuǎn)字符動畫的實例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • python實現(xiàn)飛機(jī)大戰(zhàn)小游戲

    python實現(xiàn)飛機(jī)大戰(zhàn)小游戲

    這篇文章主要為大家詳細(xì)介紹了python實現(xiàn)飛機(jī)大戰(zhàn)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • Python獲取秒級時間戳與毫秒級時間戳的示例代碼

    Python獲取秒級時間戳與毫秒級時間戳的示例代碼

    這篇文章主要介紹了Python獲取秒級時間戳與毫秒級時間戳的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Python實現(xiàn)計算字符串中出現(xiàn)次數(shù)最多的字符示例

    Python實現(xiàn)計算字符串中出現(xiàn)次數(shù)最多的字符示例

    這篇文章主要介紹了Python實現(xiàn)計算字符串中出現(xiàn)次數(shù)最多的字符,涉及Python針對字符串的遍歷、統(tǒng)計等相關(guān)操作技巧,需要的朋友可以參考下
    2019-01-01
  • 如何利用Python分析出微信朋友男女統(tǒng)計圖

    如何利用Python分析出微信朋友男女統(tǒng)計圖

    這篇文章主要給大家介紹了關(guān)于如何利用Python分析出微信朋友男女統(tǒng)計圖的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧
    2019-01-01
  • django rest framework serializer返回時間自動格式化方法

    django rest framework serializer返回時間自動格式化方法

    這篇文章主要介紹了django rest framework serializer返回時間自動格式化方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-03-03

最新評論