python包導入細節

来源:https://www.cnblogs.com/f-ck-need-u/archive/2018/11/15/9961372.html
-Advertisement-
Play Games

包導入格式 導入模塊時除了使用模塊名進行導入,還可以使用目錄名進行導入。例如,在sys.path路徑下,有一個dir1/dir2/mod.py模塊,那麼在任意位置處都可以使用下麵這種方式導入這個模塊。 一個實際一點的示例,設置PYTHONPATH環境變數為 ,然後在此目錄下創建以上目錄和mod.py ...


包導入格式

導入模塊時除了使用模塊名進行導入,還可以使用目錄名進行導入。例如,在sys.path路徑下,有一個dir1/dir2/mod.py模塊,那麼在任意位置處都可以使用下麵這種方式導入這個模塊。

import dir1.dir2.mod
from dir1.dir2.mod import XXX

一個實際一點的示例,設置PYTHONPATH環境變數為d:\pypath,然後在此目錄下創建以上目錄和mod.py文件:

set PYTHONPATH="D:\pypath"
mkdir d:\pypath\dir1\dir2
echo print("mod.py") >d:\pypath\dir1\dir2\mod.py
echo x=3 >>d:\pypath\dir1\dir2\mod.py

# 進入互動式python
>>> import dir1.dir2.mod
mod.py
>>> dir1.dir2.mod.x
3

註1:在python3.3版本及更高版本是可以導入成功的,但是在python3.3之前的版本將失敗,因為缺少__init__.py文件,稍後會解釋該文件
註2:頂級目錄dir1必須位於sys.path列出的路徑搜索列表下

如果輸出dir1和dir2,將會看到它們的是模塊對象,且是名稱空間

>>> import dir1.dir2.mod
mod.py

>>> dir1
<module 'dir1' (namespace)>

>>> dir1.dir2
<module 'dir1.dir2' (namespace)>

>>> dir1.dir2.mod
<module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>

這種模塊+名稱空間的形式就是包(嚴格地說是包的一種形式),也就是說dir1是包,dir2也是包,這種方式是包的導入形式。包主要用來組織它裡面的模塊。

從上面的結果也可以看出,包也是模塊,所以能使用模塊的地方就能使用包。例如下麵的代碼,可以像導入模塊一樣直接導入包dir2,包和模塊的區別在於它們的組織形式不一樣,模塊可能位於包內,僅此而已。

import dir1.dir2
from dir1 import dir2

另外,導入dir1.dir2.mod時,它聲明的模塊變數名為dir1,而不是dir1.dir2.mod,但是導入的對象卻包含了3個模塊:dir1、dir1.dir2以及dir1.dir2.mod。如下:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'dir1']

>>> for key in sys.modules:
...     if key.startswith("dir1"):
...             print(key,":",sys.modules[key])
...
dir1 : <module 'dir1' (namespace)>
dir1.dir2 : <module 'dir1.dir2' (namespace)>
dir1.dir2.mod : <module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>

__init__.py文件

上面的dir1和dir1.dir2目前是空包,或者說是空模塊(再一次強調,包就是模塊)。但並不意味著它們對應的模塊對象是空的,因為模塊是對象,只要是對象就會有屬性。例如,dir1包有如下屬性:

>>> dir(dir1)
['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'dir2']

之所以稱為空包,是因為它們現在僅提供了包的組織功能,而且它們是目錄,而不像py文件一樣,是實實在在的可以編寫模塊代碼的地方。換句話說,包現在是目錄文件,而不是真正的模塊文件。

為了讓包"真正的"成為模塊,需要在每個包所代表的目錄下加入一個__init__.py文件,它表示讓這個目錄格式的模塊(也就是包)像py文件一樣可以寫模塊代碼,只不過這些模塊代碼是寫入__init__.py中的。當然,模塊文件中允許沒有任何內容,所以__init__.py文件也可以是空文件,它僅表示讓包成為真正的模塊文件。

每次導入包的時候,如果有__init__.py文件,將會自動執行這個文件中的代碼,就像模塊文件一樣,事實上它就是讓目錄代表的包變成模塊的,甚至可以說它就是包所對應的模塊文件(見下麵示例),所以也可以認為__init__.py是包的初始化文件。在python3.3之前,這個文件必須存在,否則就會報錯,因為它不認為目錄是有效的模塊。

現在,在dir1和dir2下分別創建空文件__init__.py

type nul>d:\pypath\dir1\__init__.py
type nul>d:\pypath\dir1\dir2\__init__.py

現在目錄的層次格式如下:

λ tree /f d:\pypath
D:\PYPATH
└─dir1
    │  __init__.py
    └─dir2
            mod.py
            __init__.py

再去執行導入操作,並輸出包dir1和dir2。

>>> import dir1.dir2.mod
mod.py

>>> dir1
<module 'dir1' from 'd:\\pypath\\dir1\\__init__.py'>

>>> dir1.dir2
<module 'dir1.dir2' from 'd:\\pypath\\dir1\\dir2\\__init__.py'>

>>> dir1.dir2.mod
<module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>

從輸出結果中不難看出,包dir1和dir1.dir2是模塊,且它們的模塊文件是各自目錄下的__init__.py

實際上,包分為兩種:名稱空間模塊、普通模塊。名稱空間包是沒有__init__.py文件的,普通包是有__init__.py文件的。無論是哪種,它都是模塊。

__init__.py寫什麼內容

既然包是模塊,而__init__.py文件是包的模塊文件,這個文件中應該寫入什麼代碼?答案是可以寫入任何代碼,我們只需把它當作一個模塊對待就可以。不過,包既然是用來組織模塊的,真正的功能性屬性應該儘量寫入到它所組織的模塊文件中(也就是示例中的mod.py)。

但有一項__all__是應該在__init__.py文件中定義的,它是一個列表,用來控制from package import *使用*導入哪些模塊文件。這裡的*並非像想象中那樣會導入包中的所有模塊文件,而是只導出__all__列表中指定的模塊文件。

例如,在dir1.dir2包下有mod1.py、mod2.py、mod3.py和mod4.py,如果在dir2/__init__.py文件中寫入:

__all__ = ["mod1", "mod2", "mod3"]

則執行:

from dir1.dir2 import *

不會導入mod4,而是只導入mod1-mod3。

如果不設置__all__,則from dir1.dir2 import *不會導入該包下的任何模塊,但會導入dir1和dir1.dir2。

__path__屬性

嚴格地說,只有當某個模塊設置了__path__屬性時,才算是包,否則只算是模塊。這是包的絕對嚴格定義。

__path__屬性是一個路徑列表(可迭代對象即可,但通常用列表),和sys.path類似,該列表中定義了該包的初始化模塊文件__init__.py的路徑。

只要導入的是一個包(無論是名稱空間包還是普通包),首先就會設置該屬性,預設導入目錄時該屬性會初始化當前目錄,然後去該屬性列出的路徑下搜索__init__.py文件對包進行初始化。預設情況下由於__init__.py文件後執行,在此文件中可以繼續定義或修改__path__屬性,使得python會去找其它路徑下的__init__.py對模塊進行初始化。

以下是預設初始化後的__path__值:

>>> import dir1.dir2
>>> dir1.dir2.__path__
['d:\\pypath\\dir1\\dir2']

>>> import dir1.dir3
>>> dir1.dir3
<module 'dir1.dir3' (namespace)>
>>> dir1.dir3.__path__
_NamespacePath(['d:\\pypath\\dir1\\dir3'])

一般來說,幾乎不會設置__path__屬性。

導入示例

import和from導入時有多種語法可用,這兩個語句的導入方式和導入普通模塊的方式是一樣的:import導入時需要使用首碼名稱去引用,from導入時是賦值到當前程式的同名全局變數中。如果不瞭解,請看前一篇文章:python模塊導入細節

假設現在有如下目錄結構,且d:\pypath位於sys.path列表中:

$ tree -f d:\pypath
d:\pypath
└── dir1
    ├── __init__.py
    └── dir2
        ├── __init__.py
        └── mod.py

只導入包:

import dir1             # 導入包dir1
import dir1.dir2        # 導入包dir1.dir2
from dir1 import dir2   # 導入包dir1.dir2

導入某個模塊:

import dir1.dir2.mod
from dir1.dir2 import mod

如果dir2/__init__.py中設置了__all__,則下麵的導入語句會導入已設置的模塊:

from dir1.dir2 import *

註意,只支持上面這種from...import *語法,不支持import *

導入模塊中的屬性,比如變數x:

from dir1.dir2.mod import x

相對路徑導入

註:如果允許,不要使用相對路徑導入,很容易出錯,特別是對新手而言。使用絕對路徑導入,並將包放在sys.path的某個路徑下就可以。

假設現在有如下目錄結構:

$ tree -f d:\pypath
d:\pypath
└── dir1
    ├── __init__.py
    ├── dir4
    │   ├── __init__.py
    │   ├── c2.py
    │   └── c1.py
    ├── dir3
    │   ├── __init__.py
    │   ├── b3.py
    │   ├── b2.py
    │   └── b1.py
    └── dir2
        ├── __init__.py
        ├── a4.py
        ├── a3.py
        ├── a2.py
        └── a1.py

在dir1.dir2.a1模塊文件中想要導入dir1.dir3.b2模塊,可以在a1.py中使用下麵兩種方式導入:

import dir1.dir3.b2
from dir1.dir2. import b2

上面的導入方式是使用絕對路徑進行導入的,只要使用絕對路徑,都是從sys.path開始搜索的。例如,上面是從sys.path下搜索dir1,再依次搜索dir1.dir3.b2。

python還支持包的相對路徑的導入,只要使用...即可,就像操作系統上的相對路徑一樣。使用相對路徑導入時不會搜索sys.path。

相對路徑導入方式只有from...import支持,import語句不支持,且只有使用...的才算是相對路徑,否則就是絕對路徑,就會從sys.path下搜索

例如,在a1.py中導入dir1.dir3.b2:

from ..dir3 import b2

註意,必須不能直接python a1.py執行這個文件,這樣會報錯:

    from ..dir3 import b2
ValueError: attempted relative import beyond top-level package

報錯原因稍後解釋。現在在互動式模式下導入,或者使用python -m dir1.dir2.a1的方式執行。

>>> import dir1.dir2.a1

以下幾個示例都如此測試。

在a1.py中導入包dir3:

from .. import dir3

在a1.py中導入dir1.dir2.a2,也就是同目錄下的a2.py:

from . import a2

導入模塊的屬性,如變數x:

from ..dir3.b2 import x
from .a2 import x

相對路徑導入陷阱

前面說過一個相對路徑導入時的錯誤:

    from ..dir3 import b2
ValueError: attempted relative import beyond top-level package

dir3明明在dir1下,在路徑相對上,dir3確實是a1.py的../dir3,但執行python a1.py為什麼會報錯?

from ..dir3 import b2

這是因為文件系統路徑並不真的代表包的相對路徑,當在dir1/a1.py中使用..dir3,python並不知道包dir1的存在,因為沒有將它導入,沒有聲明為模塊變數,同樣,也不知道dir2的存在,僅僅只是根據語句直到了dir3的存在。但因為使用了相對路徑,不會搜索sys.path,所以它的相對路徑邊界只在本文件。所以,下麵的導入也是錯誤的:

from . import a2

實際上,更標準的解釋是,當py文件作為可執行程式文件執行時,它所在的模塊名為__main__,即__name____main__,但它並非一個包,而是一個模塊文件,對它來說沒有任何相對路徑可言。

解決方法是顯式導入它們的父包,讓python記錄它的存在,只有這樣才能使用..

python -m dir1.dir2.a2

還有幾個常見的相對路徑導入錯誤:

from .a3 import x

錯誤:

ModuleNotFoundError: No module named '__main__.a3'; '__main__' is not a package

原因是一樣的,py文件作為可執行程式文件執行時,它所在的模塊名為__main__,它並非一個包。

最後,建議在條件允許的情況下,使用絕對路徑導入,而不是相對路徑。

使用別名導入

通過包的導入方式也支持別名。例如:

from dir1.dir2.a2 import x as xx
print(xx)

import dir1.dir2.a2 as a2
print(a2.x)

from dir1.dir2 import a2 as a22
print(a22.x)

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • element vue Array數組和Map對象的添加與刪除 ...
  • DDD理解 DDD體現的是對現實的充分尊重。 1.尊重業務現實,領域專家、領域語言等概念 2.尊重團隊現實 3.尊重變化 Application 對某一業務線的整體掌控,流程組裝,進度管理,存儲時機掌控。 依賴外部模塊的業務環節實現; 儘量滿足UI需求; 落地:uow提交; Domain 業務線視作 ...
  • 一、概念 繼承的缺點:類數量爆炸、設計死板以及基類加入的新功能可能並不適用於所有的子類。 裝飾者模式:動態地將責任附加到對象上,若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。一言以蔽之 —— 動態擴展類的行為。 角色:   1、抽象組件(Component):給出一個抽象類 ...
  • 系統架構設計師-軟體水平考試高級-理論-操作系統。其中涉及進程管理(PV操作等),文件管理,存儲管理,設備管理等。 ...
  • 第1章 緒論1.1 概念模型1.2 模式世界1.2.1 Christopher Alexander1.2.2 描述格式1.2.3 關於模式的抽象程度1.3 本書中的模式1.3.1 建模實例1.3.2 模式的來源1.3.3 跨領域的模式1.4 概念模型與業務過程重組1.5 模式與框架1.6 本書的使用 ...
  • 系統: CentOS 7 IP: 192.168.11.199關閉 selinux 和防火牆 # setenforce 0 # 臨時關閉,重啟後失效 # systemctl stop firewalld.service # 臨時關閉,重啟後失效 修改字元集,否則可能報 input/output er ...
  • 歡迎到我的簡書查看我的文集 前言: 是圖形用戶界面,在 中,圖形用戶界面我們用 表示,而 的完整英文為: (圖形用戶介面), 所謂圖形用戶界面就是以圖形的方式來顯示你電腦的操作界面, 我們電腦中操作的界面就是 我們 中常說的圖形用戶界面, 這樣的操作簡單明瞭. 的英文為 , 是命令行用戶介面, ...
  • 51.HashMap的實現原理 HashMap的主幹是一個Entry數組。 Entry是HashMap的基本組成單元, 每一個Entry包含一個key-value鍵值對。 HashMap基於hashing原理, 我們通過put()和get()方法儲存和獲取對象。 當我們將鍵值對傳遞給put()方法時 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...