當(dāng)前目錄 和 腳本目錄
參考資料:https://techibee.com/python/get-current-directory-using-python/2790
current directory/working directory(工作目錄):運(yùn)行python時(shí)用戶所在的目錄,就是用戶打shell命令的pwd看到的目錄,與python腳本在哪里無關(guān)(比如腳本文件在../../script.py)
os.getcwd():可以獲得current dir
工作目錄的作用是:讀取文件時(shí),open('filename')指的是工作目錄下的filename文件,open('../filename')指的就是在工作目錄上一層的filename文件。
入口文件及所在目錄:
__file__: 腳本文件路徑
則腳本目錄可以這樣獲得:os.path.dirname(os.path.realpath(__file__))
導(dǎo)入詳解
參考資料:https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html
python的import一直對我是個(gè)黑洞,我只是大致了解sys.path的功能。但是python的導(dǎo)入實(shí)際上是有很多學(xué)問的,弄懂它對開發(fā)運(yùn)維都有巨大幫助, 我從上面這位斯坦福大學(xué)大二學(xué)生的教程中受益匪淺,決定把它編譯下來,順帶改正幾處他的筆誤。讀懂了上文,只要你不準(zhǔn)備寫第三方庫,那么基本能夠應(yīng)付幾乎所有的導(dǎo)入問題了。
關(guān)鍵點(diǎn):
-
import語句根據(jù)sys.path的路徑進(jìn)行搜索 -
sys.path自動(dòng)包含入口文件所在目錄,而不是包含working directory -
import一個(gè)package概念上就是導(dǎo)入該package的__init__.py文件
基本概念
- module:
*.py文件,module的名字為該文件的名字 - built-in module: 被編譯進(jìn)python解釋器的模塊(用C實(shí)現(xiàn)),因此沒有對應(yīng)的
*.py文件 - package: 包含
__init__.py的目錄,package的名字是目錄的名字- python 3.3以后,任何目錄都是package(即使沒有
__init__.py)
- python 3.3以后,任何目錄都是package(即使沒有
例子的目錄結(jié)構(gòu)
test/ # root folder
packA/ # package packA
subA/ # subpackage subA
__init__.py
sa1.py
sa2.py
__init__.py
a1.py
a2.py
packB/ # package packB (implicit namespace package)
b1.py
b2.py
time.py
random.py
other.py
start.py
import做了什么?
當(dāng)import module時(shí),python會(huì)運(yùn)行其中的代碼;當(dāng)import package時(shí),python會(huì)運(yùn)行__init__.py中的代碼
import的搜索路徑
當(dāng)
import spam時(shí),先搜索built-in module中是否有spam模塊,如果沒有則按順序搜索sys.path下的是由有spam模塊。假設(shè)運(yùn)行
>> python script.py,sys.path的路徑列表是這樣初始化的的:
- 包含入口文件script.py的目錄 (如果沒有指定script.py,直接
>> python, 則是當(dāng)前目錄).PYTHONPATH(目錄列表,格式同shell變量PATH)- 默認(rèn)的package包(如標(biāo)注庫)
初始化完成后,python程序可以更改sys.path. 此外因?yàn)槿肟谖募谀夸浥旁跇?biāo)準(zhǔn)庫之前,因此與標(biāo)準(zhǔn)庫同名的在該目錄下的自定義文件會(huì)替代標(biāo)準(zhǔn)庫被優(yōu)先導(dǎo)入。 Source: Python 2 and 3.
要注意的是python解釋器會(huì)優(yōu)先從built-in module中搜索module,找不到再從sys.path中搜索。built-in module可由命令sys.builtin_module_names獲得,如
sys, time這些模塊都是built-in module. (注意built-in module與built-in function有區(qū)別, built-in function可以在builtins module找到,而builtins本身又是built-in module.)
如上面的目錄結(jié)構(gòu)中:time是built-in module, 而random是 standard module, 因此在start.py文件中import time會(huì)導(dǎo)入的python的內(nèi)置module,但是 import random會(huì)導(dǎo)入我們自定義的module。
sys.path注意點(diǎn)
最后強(qiáng)調(diào)一遍:當(dāng)運(yùn)行一個(gè)python腳本時(shí),sys.path不關(guān)心工作目錄(當(dāng)前目錄)在哪里,只關(guān)心入口文件所在的目錄在哪里。例如:如果當(dāng)前目錄在test/下,執(zhí)行python ./packA/subA/subA1.py, sys.path[0]是test/packA/subA/而不是test/。
此外sys.path是導(dǎo)入模塊所共享的。例如假設(shè)我們執(zhí)行python start.py, 而start.py中執(zhí)行了import packA.a1, 在a1中打印sys.path, 則它包含test/目錄(入口文件start.py的路徑), 而不是test/packA/(a1,py的路徑); 因此在a1.py中導(dǎo)入other應(yīng)該執(zhí)行import other ,因?yàn)?code>other.py在搜索路徑test/中。
__init__.py詳解
__init__.py有以下2個(gè)功能:
- 將目錄轉(zhuǎn)化為可導(dǎo)入的package(python3.3及其之前)
- 執(zhí)行package的初始化代碼
將目錄轉(zhuǎn)化為可導(dǎo)入的package
要導(dǎo)入不在入口文件所在路徑的module或package,該module需要在package中。
而package在python3.3之前都需要包含一個(gè)__init__.py文件,該文件可以為空。比如用python2.7運(yùn)行start.py,它可以import packA, 但是不能import packB, 因?yàn)樵?test/packB中不存在__init__.py。
不過在python3.3及其以后的版本,所有目錄都被認(rèn)為是package。假如用pythno3.6運(yùn)行以下命令,輸入如下:
>>> import packB
>>> packB
<module 'packB' (namespace)>
運(yùn)行package的初始化代碼
每當(dāng)import package時(shí),python會(huì)先執(zhí)行__init__.py中的代碼。任何在__init__.py中定義的對象或文件,都在該包的namspace中。
例如:
test/packA/a1.py
def a1_func():
print("running a1_func()")
test/packA/__init__.py
## this import makes a1_func directly accessible from packA.a1_func
from packA.a1 import a1_func
def packA_func():
print("running packA_func()")
test/start.py
import packA # "import packA.a1" will work just the same
packA.packA_func()
packA.a1_func()
packA.a1.a1_func()
python start.py輸出為:
running packA_func()
running a1_func()
running a1_func()
導(dǎo)入package
導(dǎo)入包含__inti__.py的package概念上等效于導(dǎo)入__init__.py作為一個(gè)模塊,這確實(shí)也是python的處理方式,從以下輸出可以看出來:
>>> import packA
>>> packA
<module 'packA' from 'packA\__init__.py'>
absolute導(dǎo)入與relative導(dǎo)入:重點(diǎn)
absolute導(dǎo)入:使用全路徑導(dǎo)入(從入口文件所在位置算起)
relative導(dǎo)入:以當(dāng)前模塊(即腳本)作為相對位置來導(dǎo)入
其中relative導(dǎo)入分為2種:
- explicit relative imports: 以
from .<module/package> import X形式的導(dǎo)入,前面的點(diǎn)表示當(dāng)前目錄,兩個(gè)點(diǎn)表示上一層目錄。。。 - implicit relative import: 就好像當(dāng)前目錄在
sys.path中的導(dǎo)入,它只適用于python 2, python3中不再支持:
The only acceptable syntax for relative imports is from .[module] import name. All import forms not starting with . are interpreted as absolute imports.
Source: What’s New in Python 3.0
例子如下:
假設(shè)運(yùn)行入口文件是start.py, 它導(dǎo)入了a1, 而a1又導(dǎo)入了other, a1和sa1, 則a1中的導(dǎo)入方式可以這樣寫:
- absolute imports:
import other
import packA.a2
import packA.subA.sa1
- explict relative imports:
import other # absolute imports
from . import a2 # explict relative imports
from .subA import sa1 # explict relative imports
- implicit relative imports(python3不支持)
import other # absolute imports
import a2 # implicit relative imports
import subA.sa1 # implicit relative imports
需要注意的是, .只能上溯至入口文件所在目錄(但不包括),因此from .. import other是不支持的,會(huì)報(bào):ValueError: attempted relative import beyond top-level package。 因此只能用absolute import。
建議只使用absolute import , 不僅是因?yàn)槊魑锥€因?yàn)橄鄬?dǎo)入的文件都無法直接運(yùn)行,而絕對導(dǎo)入的module可以通過某種方式運(yùn)行:將原來的入口文件所在目錄添加到sys.path中,下文將做詳細(xì)分析。
案例
Case1 不修改sys.path:
python start.py時(shí),sys.path總包含test/目錄,導(dǎo)入sa1.py中的helloWorld函數(shù), 使用絕對路徑導(dǎo)入:
from packA.subA.sa1 import helloWorld
Case2 可以修改sys.path:
假設(shè)start.py中導(dǎo)入了a2, a2中導(dǎo)入了sa2,start.py永遠(yuǎn)需要直接運(yùn)行;不過我們有時(shí)候也希望能夠直接運(yùn)行a2。
但是問題是,當(dāng)我們運(yùn)行start.py時(shí),sys.path中包含的目錄是test/,但是當(dāng)我們運(yùn)行a2時(shí),sys.path中包含的目錄是test/packA/。
當(dāng)我們直接運(yùn)行start.py時(shí),在a2中要導(dǎo)入sa2, 導(dǎo)入語句是from PackA.subA import sa2; 但是直接運(yùn)行a2時(shí),上述導(dǎo)入方式就會(huì)報(bào)錯(cuò),因?yàn)?code>test/不在搜索路徑中了,必須這樣導(dǎo)入: from subA import sa2. 不過這樣的導(dǎo)入語句如果運(yùn)行start.py時(shí)又會(huì)報(bào)錯(cuò)(Python3),因?yàn)?code>test/packA不在搜索路徑中(不過python2的implicit relative import不報(bào)錯(cuò),不過我們今后基本不再用python2, 而且根據(jù)python之禪:盡量使用唯一的最好方式,最好使用絕對導(dǎo)入)。
總結(jié)一下:
| Run | from packA.subA import sa2 | from subA import sa2 |
|---|---|---|
| start.py | OK | Py2 OK, Py3 fail (subA not in test/) |
| a2.py | fail (packA not in test/packA/) | OK |
從上表看出,a2無論哪種導(dǎo)入sa2的方式,要么運(yùn)行start.py時(shí)報(bào)錯(cuò),要么運(yùn)行a2時(shí)報(bào)錯(cuò),沒有同時(shí)都可以運(yùn)行成功的方案。
下面提供了3種方案:
- 使用
from packA.subA import sa2(中間一列),此時(shí)start.py當(dāng)然沒問題; 將命令行切換到test/目錄下,運(yùn)行python -m packA.a2,就等于直接運(yùn)行a2了。 - 使用
from packA.subA import sa2(中間一列),此時(shí)start.py當(dāng)然沒問題;我們可以在運(yùn)行a2前更改sys.path,將test加入搜索路徑, 這樣直接運(yùn)行a2也OK了。
# a2.py
import os, sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
# now this works, even when a2.py is run directly
from packA.subA import sa2
需要注意的是,如果python沒有配置好,__file__有時(shí)候不準(zhǔn)確,可以使用 built-in inspect包來解決(this StackOverflow answer)。
- 使用第三列的寫法,可是只對python2有效
- 使用
from packA.subA import sa2(中間一列),同時(shí)將test/加入到PYTHONPATH環(huán)境變量。
根據(jù)python之禪,我推薦使用第二種方案。
There should be one-- and preferably only one --obvious way to do it.
Case3
a2還是要導(dǎo)入sa2, 但這次我想直接運(yùn)行a1,而非a2(start.py當(dāng)然也要直接運(yùn)行);則仍然可以用2,3,4的方案,但方案1不再起作用。
Case4:導(dǎo)入父級目錄的module
例如,想直接運(yùn)行sa1, 而sa1想導(dǎo)入a1, 此時(shí)只能通過修改sys.path或PYTHONPATH來做到,就是用方案2, 4。
但是我建議:寫代碼時(shí),盡量不要導(dǎo)入父級目錄的腳本。
其實(shí)pycharm的導(dǎo)入方式,就是修改了PYTHONPATH和sys.path,不過它設(shè)置的地方比較多,細(xì)節(jié)待整理:



python2與python3的區(qū)別
- python2支持
implicit relative import, python3不支持 - python2的package要包含
__init__.py,python3.3和更高版本把所有目錄都認(rèn)為是package(implicit namespace packages) -
from <module> import *語法在python2中可以寫在函數(shù)中,python3只允許寫在module一級。
其他散落的知識點(diǎn)
-
__init__.py中使用__all__
-
pip install -e <project>將project的root目錄加入sys.path
-
from <module> import *不會(huì)將'_'開頭的名稱導(dǎo)入
- 使用
if __name__ == '__main__'檢測腳本是直接運(yùn)行還是被導(dǎo)入的
總結(jié)
對于一般的開發(fā)工作,上文介紹python的導(dǎo)入機(jī)制已經(jīng)足夠用了。
但是如果你想開發(fā)package并且發(fā)布出去,那么再深入研究一番也是必須的。事實(shí)上,本文有大量知識點(diǎn)都有待深入挖掘,此外我覺得還可以研究一下pkgutil和importlib標(biāo)準(zhǔn)庫模塊。
補(bǔ)充:
寫此文已過去2個(gè)月,我在import logging的時(shí)候又遇到了新的問題:
import logging
logging.config.fileConfig('logging.ini')
上文報(bào)AttributeError: module 'logging' has no attribute 'config'.
而我這樣導(dǎo)入就沒問題, 而且可以直接使用loggin.getLogger:
import logging.config
logging.config.fileConfig('logging.ini')
logger = logging.getLogger(__name__)
其實(shí)看一下logging模塊就知道了:
logging $tree
.
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-37.opt-1.pyc
│ ├── __init__.cpython-37.pyc
│ ├── config.cpython-37.opt-1.pyc
│ ├── config.cpython-37.pyc
│ ├── handlers.cpython-37.opt-1.pyc
│ └── handlers.cpython-37.pyc
├── config.py
└── handlers.py
而__init__.py文件如下:

config是一個(gè)單獨(dú)的模塊,即使導(dǎo)入了logging,該模塊也未導(dǎo)入,因此不能使用;而getLogger定義在__init__.py中,導(dǎo)入logging.config的時(shí)候也導(dǎo)入了logging,因此__init__.py中的任何函數(shù)都可以直接使用。
如果只是使用getLogger,只導(dǎo)入logging而不導(dǎo)入logging.config也是可以的:
import logging
logger = logging.getLogger(__name__)
這篇stackoverflow的回答也是這么說的:https://stackoverflow.com/questions/2234982/why-both-import-logging-and-import-logging-config-are-needed
后記
導(dǎo)入更深入的解釋可以看此文,有視頻可以幫助理解:Modules and Packages: Live and Let Die!
http://www.dabeaz.com/modulepackage/index.html