一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Python 的 import 機(jī)制

 望穿墻 2018-03-01

轉(zhuǎn): https:///posts/python-de-import-ji-zhi.html

一直對 Python 的 import 機(jī)制不甚了解,這次在寫 Easy-Karabiner 的時候就踩坑了,順便了解了一下,發(fā)現(xiàn)這玩意兒還不是那么「符合直覺」,遂寫篇博客講講。

模塊與包

在了解 import 之前,有兩個概念必須提一下:

  • 模塊: 一個 .py 文件就是一個模塊(module)
  • 包: __init__.py 文件所在目錄就是包(package)

當(dāng)然,這只是極簡版的概念。實際上包是一種特殊的模塊,而任何定義了 __path__ 屬性的模塊都被當(dāng)做包。只不過,咱們?nèi)粘J褂弥胁⒉恍枰肋@些。

兩種形式的 import

import 有兩種形式:

  • import ...
  • from ... import ...

兩者有著很細(xì)微的區(qū)別,先看幾行代碼。

1
2
3
from string import ascii_lowercase
import string
import string.ascii_lowercase

運行后發(fā)現(xiàn)最后一行代碼報錯:ImportError: No module named ascii_lowercase,意思是:“找不到叫 ascii_lowercase 的模塊”。第 1 行和第 3 行的區(qū)別只在于有沒有 from,翻翻語法定義發(fā)現(xiàn)有這樣的規(guī)則:

  • import ... 后面只能是模塊或包
  • from ... import ... 中,from 后面只能是模塊或包,import 后面可以是任何變量

可以簡單的記成:第一個空只能填模塊或包,第二個空填啥都行

import 的搜索路徑

提問,下面這幾行代碼的輸出結(jié)果是多少?

1
2
3
# foo.py
import string
print(string.ascii_lowercase)

是小寫字母嗎?那可不一定,如果目錄樹是這樣的:

1
2
3
./
├── foo.py
└── string.py

foo.py 所在目錄有叫 string.py 的文件,結(jié)果就不確定了。因為你不知道 import string 到底是 import 了 ./string.py 還是標(biāo)準(zhǔn)庫的 string。為了回答這個問題,我們得了解一下 import 是怎么找到模塊的,這個過程比較簡單,只有兩個步驟:

  1. 搜索「內(nèi)置模塊」(built-in module)
  2. 搜索 sys.path 中的路徑

sys.path 在初始化時,又會按照順序添加以下路徑:

  1. foo.py 所在目錄(如果是軟鏈接,那么是真正的 foo.py 所在目錄)或當(dāng)前目錄;
  2. 環(huán)境變量 PYTHONPATH中列出的目錄(類似環(huán)境變量 PATH,由用戶定義,默認(rèn)為空);
  3. site 模塊被 import 時添加的路徑1site 會在運行時被自動 import)。

import site 所添加的路徑一般是 XXX/site-packages(Ubuntu 上是 XXX/dist-packages),比如在我的機(jī)器上是 /usr/local/lib/python2.7/site-packages。同時,通過 pip 安裝的包也是保存在這個目錄下的。如果懶得記 sys.path 的初始化過程,可以簡單的認(rèn)為 import 的查找順序是:

  1. 內(nèi)置模塊
  2. .py 文件所在目錄
  3. pipeasy_install 安裝的包

回到前面的問題,因為 import string 是通過搜尋 foo.py 文件所在目錄,找到 string.py 后 import 的,所以輸出取決于 import string.py 時執(zhí)行的代碼。

相對 import 與 絕對 import

相對 import

當(dāng)項目規(guī)模變大,代碼復(fù)雜度上升的時候,我們通常會把一個一個的 .py 文件組織成一個包,讓項目結(jié)構(gòu)更加清晰。這時候 import 又會出現(xiàn)一些問題,比如:一個典型包的目錄結(jié)構(gòu)是這樣的:

1
2
3
4
string/
├── __init__.py
├── find.py
└── foo.py

如果 string/foo.py 的代碼如下:

1
2
3
# string/foo.py
from string import find
print(find)

那么 python string/foo.py 的運行結(jié)果會是下面的哪一個呢?

  • <module 'string.find' from 'string/find.py'>
  • <function find at 0x123456789>

按我們前面講的各種規(guī)則來推導(dǎo),因為 foo.py 所在目錄 string/ 沒有 string 模塊(即 string.py),所以 import 的是標(biāo)準(zhǔn)庫的 string,答案是后者。不過,如果你把 foo 當(dāng)成 string 包中的模塊運行,即 python -m string.foo,會發(fā)現(xiàn)運行結(jié)果是前者。同樣的語句,卻有著兩種不同的語義,這無疑加重了咱們的心智負(fù)擔(dān),總不能每次咱們調(diào)試包里的模塊時,都去檢查一下執(zhí)行的命令是 python string/foo.py 還是 python -m string.foo 吧?

相對 import 就是專為解決「包內(nèi)導(dǎo)入」(intra-package import)而出現(xiàn)的。它的使用也很簡單,from 的后面跟個 . 就行:

1
from .XXX import ...

比如:

1
2
3
4
# from string/ import find.py
from . import find
# from string/find.py import *
from .find import *

我們再看個復(fù)雜點的例子,有個包的目錄結(jié)構(gòu)長這樣:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
one/
├── __init__.py
├── foo.py
└── two/
    ├── __init__.py
    ├── bar.py
    └── three/
        ├── __init__.py
        ├── dull.py
        └── run.py

foo.py、bar.py、dull.py 中的代碼分別是 print(1)、print(2)print(3),并且 run.py 的代碼如下:

1
2
3
4
from . import dull
from .. import bar
from ... import foo
print('Go, go, go!')

我們通過 python -m one.two.three.run 運行 run.py,可以看到 run.py 運行結(jié)果如下:

1
2
3
4
3
2
1
Go, go, go!

意思就是,from 后面出現(xiàn)幾個 . 就表示往上找第幾層的包。也可以將 run.py 改寫成下面這樣,運行結(jié)果是一樣的:

1
2
3
4
from .dull import *
from ..bar import *
from ...foo import *
print('Go, go, go!')

好啦,相對 import 就介紹到這里,回到最初的問題。如果用相對 import,把 string/foo.py 改寫成:

1
2
3
# string/foo.py
from . import find
print(find)

那么 python string/foo.pypython -m string.foo 的運行結(jié)果又是怎樣呢?運行一下發(fā)現(xiàn),兩者的輸出分別是:

1
2
3
4
Traceback (most recent call last):
  File "string/foo.py", line 1, in <module>
    from . import find
ValueError: Attempted relative import in non-package
1
<module 'string.find' from 'string/find.py'>

原因在于 python string/foo.pyfoo.py 當(dāng)成一個單獨的腳本來運行,認(rèn)為 foo.py 不屬于任何包,所以此時相對 import 就會報錯。也就是說,無論命令行是怎么樣的,運行時 import 的語義都統(tǒng)一了,不會再出現(xiàn)運行結(jié)果不一致的情況。

絕對 import

絕對 import 和相對 import 很好區(qū)分,因為從行為上來看,絕對 import 會通過搜索 sys.path 來查找模塊;另一方面,除了相對 import 就只剩絕對 import 了嘛 :) 也就是說:

  1. 所有的 import ... 都是絕對 import
  2. 所有的 from XXX import ... 都是絕對 import

不過,第 2 點只對 2.7 及其以上的版本(包括 3.x)成立喔!如果是 2.7 以下的版本,得使用

1
from __future__ import absolute_import

兩者的差異

首先,絕對 import 是 Python 默認(rèn)的 import 方式,其原因有兩點:

  • 絕對 import 比相對 import 使用更頻繁
  • 絕對 import 能實現(xiàn)相對 import 的所有功能

其次,兩者搜索模塊的方式不一樣:

  • 對于相對 import,通過查看 __name__ 變量,在「包層級」(package hierarchy)中搜索
  • 對于絕對 import,當(dāng)不處于包層級中時,搜索 sys.path

前面在介紹 sys.path 的初始化的時候,我在有個地方故意模棱兩可,即:

foo.py 所在目錄(如果是軟鏈接,那么是真正的 foo.py 所在目錄)或 當(dāng)前目錄

官方文檔的原文是:

the directory containing the input script (or the current directory).

這是因為當(dāng)模塊處于包層級中的時候,絕對 import 的行為比較蛋疼,官方的說法是:

The submodules often need to refer to each other. For example, the surround module might use the echo module. In fact, such references are so common that the import statement first looks in the containing package before looking in the standard module search path. Thus, the surround module can simply use import echo or from echo import echofilter. If the imported module is not found in the current package (the package of which the current module is a submodule), the import statement looks for a top-level module with the given name.

但是在我的測試中發(fā)現(xiàn),其行為可能是下面兩者中的任意一種:

  • .py 文件所在目錄
  • 當(dāng)前目錄

比如,對于目錄結(jié)構(gòu)如下的包:

1
2
3
4
5
6
7
8
father/
├── __init__.py
├── child/
│   ├── __init__.py
│   ├── foo.py
│   └── string.py
└── string/
    └── __init__.py

其中,foo.py 代碼如下:

1
2
import string
print(string)

import string 真正導(dǎo)入的模塊是:

version python -m child.foo python child/foo.py
2.7.11 child/string.py child/string.py
3.5.1 string/__init__.py child/string.py

如果將 foo.py 的代碼改成(你可以 print(sys.path) 看看為什么改成這樣):

1
2
3
4
import sys
sys.path[0] = ''
import string
print(string)

import 的模塊就變成了:

version python -m child.foo python child/foo.py
2.7.11 child/string.py string/__init__.py
3.5.1 string/__init__.py string/__init__.py

為了避免踩到這種坑,咱們可以這樣子:

  • 避免包或模塊重名,避免使用 __main__.py
  • 包內(nèi)引用盡量使用相對 import

import 的大致過程

import 的實際過程十分復(fù)雜,不過其大致過程可以簡化為:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def import(module_name):
    if module_name in sys.modules:
        return sys.modules[module_name]
    else:
        module_path = find(module_name)

        if module_path:
            module = load(module_path)
            sys.modules[module_name] = module
            return module
        else:
            raise ImportError

sys.modules 用于緩存,避免重復(fù) import 帶來的開銷;load 會將模塊執(zhí)行一次,類似于直接運行。

Tips

  • import 會生成 .pyc 文件,.pyc 文件的執(zhí)行速度不比 .py 快,但是加載速度更快
  • 重復(fù) import 只會執(zhí)行第一次 import
  • 如果在 ipython 中 import 的模塊發(fā)生改動,需要通過 reload 函數(shù)重新加載
  • import * 會導(dǎo)入除了以 _ 開頭的所有變量,但是如果定義了 __all__,那么會導(dǎo)入 __all__ 中列出的東西

參考


  1. 官方說法是「Python 安裝時設(shè)定的默認(rèn)路徑」(The installation-dependent default path),而這玩意兒實際上是通過 site 模塊來設(shè)置的。 ?

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    欧美精品亚洲精品日韩精品| 欧美精品在线观看国产| 欧美偷拍一区二区三区四区| 国产欧美日韩精品成人专区| 欧美日韩有码一二三区| 中文字幕亚洲精品人妻| 91偷拍视频久久精品| 亚洲精品偷拍视频免费观看| 91人妻人澡人人爽人人精品| 国产精品偷拍视频一区| 欧美成人黄色一区二区三区| 日韩人妻欧美一区二区久久| 日本一区二区三区黄色| 99久久免费看国产精品| 亚洲一区二区三区日韩91| 亚洲一区二区福利在线| 国产熟女高清一区二区| 日本东京热加勒比一区二区| 国产白丝粉嫩av在线免费观看| 亚洲成人免费天堂诱惑| 高潮少妇高潮久久精品99| 亚洲一区二区三区福利视频| 日韩在线中文字幕不卡| 色婷婷在线精品国自产拍| 一区二区三区亚洲天堂| 国产精品视频第一第二区| 国产又大又硬又粗又湿| 好吊妞视频只有这里有精品| 日韩和欧美的一区二区三区| 日韩精品你懂的在线观看| 老熟女露脸一二三四区| 国产精品亚洲综合天堂夜夜| 国产免费一区二区三区不卡| 加勒比日本欧美在线观看| 欧美日韩最近中国黄片| 日韩不卡一区二区视频| 在线观看国产午夜福利| 国产精品一区二区有码| 欧美夫妻性生活一区二区| 人妻内射在线二区一区| 色偷偷偷拍视频在线观看|