
來源:麥叔編程
作者:麥叔
學習方法不對,事倍功半!學習方法對了,事半功倍。
學編程,要先扎實的學好基礎語法和結構,剩下的就是不斷的實戰應用,同時按需加強相關知識。
Python的包就是這里說的基礎語法結構之一。
把手放在胸口上,問問自己,你對Python包的了解有多少?然后認真看完本文。你的今天一定是有進步的。
包是基于模塊的,是對模塊的組織,建議和另一篇模塊文章一起看,融會貫通起來。模塊文章鏈接見文末往期推薦第1篇。
假設你已經開發了一個包含許多模塊的非常大的應用程序。隨著模塊數量的增長,如果將它們都放到一個位置,則很難跟蹤所有模塊。如果它們有相似的名稱或功能,情況會更糟。你可能希望把他們放在不同的文件夾中,這就是Python中的包。
包(package)允許使用點表示法對模塊名稱空間進行分層結構。就像模塊可以避免全局變量名之間的沖突一樣,包也可以避免模塊名之間的沖突。
創建包非常簡單,因為它利用了操作系統固有的分層文件結構。參考下面的目錄結構:
pkg ├── mod1.py └── mod2.py
這里有一個名為pkg的目錄,其中包含兩個模塊,mod1.py和mod2.py。模塊的內容有:
mod1.py
def foo(): print('[mod1] foo()') class Foo: pass
mod2.py
def bar(): print('[mod2] bar()') class Bar: pass
根據這個結構,如果pkg目錄位于一個可以找到它的位置(在sys.path中包含的一個目錄中),你可以用點符號引用這兩個模塊(pkg.mod1, pkg.mod2),然后用你已經熟悉的語法導入它們:
import <module_name>[, <module_name> ...]
>>> import pkg.mod1, pkg.mod2 >>> pkg.mod1.foo()
[mod1] foo() >>> x = pkg.mod2.Bar() >>> x
0x033F7290>
from import
>>> from pkg.mod1 import foo >>> foo()
[mod1] foo()
from import as
>>> from pkg.mod2 import Bar as Qux >>> x = Qux() >>> x
0x036DFFD0>
你也可以用這些語句來導入模塊:
from <package_name> import <modules_name>[, <module_name> ...]
from <package_name> import <module_name> as <alt_name>
>>> from pkg import mod1 >>> mod1.foo()
[mod1] foo() >>> from pkg import mod2 as quux >>> quux.bar()
[mod2] bar()
從技術上講,你也可以直接導入這個包:
>>> import pkg >>> pkg
<module 'pkg' (namespace)>
但這沒什么用。盡管嚴格地說,這是一個語法正確的Python語句,但它并沒有把pkg中的任何模塊放到本地命名空間中:
>>> pkg.mod1
Traceback (most recent call last):
File "" , line 1, in <module> pkg.mod1 AttributeError: module 'pkg'
has no attribute 'mod1' >>> pkg.mod1.foo()
Traceback (most recent call last):
File "" , line 1, in <module> pkg.mod1.foo() AttributeError: module '
pkg' has no attribute 'mod1' >>> pkg.mod2.Bar()
Traceback (most recent call last):
File "" , line 1, in <module> pkg.mod2.Bar() AttributeError: module '
pkg' has no attribute 'mod2'
要實際導入模塊或其內容,需要使用上面展示的import例子。
如果一個名為__init__.py的文件存在于包目錄中,它會在導入包或包中的模塊時被調用。這可以用于執行包初始化代碼,比如包級數據的初始化。
例如以下__init__.py文件:
__init__.py
print(f'Invoking __init__.py for {__name__}')
A = ['quux', 'corge', 'grault']
讓我們把上面例子中的這個文件添加到pkg目錄中:
pkg ├── __init__.py ├── mod1.py └── mod2.py
現在,當包被導入時,A就會被初始化:
>>> import pkg
Invoking __init__.py for pkg >>> pkg.A
['quux', 'corge', 'grault']
包中的模塊可以訪問包里的全局變量:
mod1.py
def foo(): from pkg import A
print('[mod1] foo() / A = ', A) class Foo: pass
>>> from pkg import mod1
Invoking __init__.py for pkg >>> mod1.foo()
[mod1] foo() / A = ['quux', 'corge', 'grault']
__init__.py也可以用來實現從包中自動導入模塊。例如,前面你看到import pkg語句只將名稱pkg放在調用者的局部符號表中,而不導入任何模塊。但是如果pkg目錄中的__init__.py包含以下內容:
__init__.py
print(f'Invoking __init__.py for {__name__}') import pkg.mod1, pkg.mod2
然后當你執行import pkg,模塊mod1和mod2自動導入:
>>> import pkg Invoking __init__.py for pkg >>> pkg.mod1.foo() [mod1] foo()
>>> pkg.mod2.bar() [mod2] bar()
注意:大部分Python文檔都聲明在創建包時必須在包目錄中存在__init__.py文件。這曾經是必須的。過去,__init__.py的存在對Python來說意味著正在定義一個包。該文件可以包含初始化代碼,甚至可以為空,但它必須存在。從Python 3.3開始,引入了隱式命名空間包。這些允許創建一個沒有任何__init__.py文件的包。當然,如果需要包初始化,它仍然可以存在。但現在不再是必須的了。
為了以下討論的目的,先前定義的包被擴展以包含一些額外的模塊:
pkg ├── mod1.py ├── mod2.py ├── mod3.py └── mod4.py
pkg目錄中現在定義了四個模塊。其內容如下:
mod1.py
def foo(): print('[mod1] foo()') class Foo: pass
mod2.py
def bar(): print('[mod2] bar()') class Bar: pass
mod3.py
def baz(): print('[mod3] baz()') class Baz: pass
mod4.py
def qux(): print('[mod4] qux()') class Qux: pass
正如你所看到,當import *用于一個模塊時,該模塊中的所有對象都被導入到本地符號表中,除了那些名稱以下劃線開頭的對象:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__'] >>> from pkg.mod3 import *
>>> dir()
['Baz', '__annotations__', '__builtins__', '__doc__', '__loader__',
'__name__', '__package__', '__spec__', 'baz'] >>> baz()
[mod3] baz() >>> Baz
<class 'pkg.mod3.Baz'>
一個包的類似聲明是這樣的:
from import *
這行代碼做了什么呢?
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__'] >>> from pkg import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__',
'__name__', '__package__', '__spec__']
嗯。好像什么也沒做。你可能期望Python會深入到包目錄中,找到它所能找到的所有模塊,并將它們全部導入。但正如你所看到的,默認情況下并不是這樣的。
相反,Python遵循以下約定:如果包目錄中的__init__.py文件包含名為__all__的列表,當遇到import *語句時,它將被視為應該導入的模塊列表。
對于現在的例子,假設你像這樣在pkg目錄中創建一個__init__.py:
pkg/__init__.py
__all__ = [
'mod1',
'mod2',
'mod3',
'mod4' ]
現在用import *導入所有四個模塊:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__'] >>> from pkg import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'mod1', 'mod2', 'mod3', 'mod4'] >>> mod2.bar()
[mod2] bar() >>> mod4.Qux
<class 'pkg.mod4.Qux'>
使用import *仍然不被認為是很好的形式,無論是對包還是模塊來說都是如此。但是這個功能至少讓包的創建者對指定import *時發生的事情有一定的控制。(事實上,它提供了完全禁止它的能力,只要拒絕定義__all__就行了。如你所見,包的默認行為是不導入任何內容。)
順便說一下,__all__也可以在模塊中定義,并達到同樣的目的:控制import *導入的內容。例如,修改mod1.py如下: pkg/mod1.py
__all__ = ['foo'] def foo(): print('[mod1] foo()') class Foo: pass
現在,pkg.mod1中的import *語句只會導入包含在__all__中的內容:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__'] >>> from pkg.mod1 import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__',
'__name__', '__package__', '__spec__', 'foo'] >>> foo()
[mod1] foo() >>> Foo
Traceback (most recent call last):
File "" , line 1, in <module> Foo NameError: name 'Foo' is not defined
foo()(函數)現在定義在本地命名空間中,但foo(類)沒有定義,因為后者不在__all__中。
總之,當import *被指定時,__all__會被包和模塊用來控制導入的內容。但是默認行為是不同的:
對于一個包:當__all__沒有定義,import *不導入任何東西。對于一個模塊:當__all__沒有定義,import *導入所有內容(除了以下劃線開頭的名稱)。
包可以包含任意深度的嵌套子包。例如,讓我們對示例包目錄再做一個修改,如下所示:
pkg ├── sub_pkg1 │ ├── mod1.py │ └── mod2.py └── sub_pkg2
├── mod3.py └── mod4.py
四個模塊(mod1.py, mod2.py, mod3.py和mod4.py)的定義如前所述。但是現在,它們不是被集中到pkg目錄中,而是被分成兩個子目錄,sub_pkg1和sub_pkg2。
導入仍然和前面顯示的一樣工作。語法類似,但是額外的點符號用于分隔包名和子包名:
>>> import pkg.sub_pkg1.mod1 >>> pkg.sub_pkg1.mod1.foo()
[mod1] foo() >>> from pkg.sub_pkg1 import mod2 >>> mod2.bar()
[mod2] bar() >>> from pkg.sub_pkg2.mod3 import baz >>> baz()
[mod3] baz() >>> from pkg.sub_pkg2.mod4 import qux as grault >>> grault()
[mod4] qux()
此外,一個子包中的模塊可以引用同級子包中的對象(如果同級子包包含你需要的某些功能)。例如,假設你想從mod3模塊中導入并執行mod1中的函數foo()。你可以使用絕對導入:
pkg/sub_pkg2/mod3.py
def baz(): print('[mod3] baz()') class Baz: pass
from pkg.sub_pkg1.mod1 import foo foo()
>>> from pkg.sub_pkg2 import mod3 [mod1] foo()
>>> mod3.foo() [mod1] foo()
或者你可以使用相對導入,其中..指的是上一級的包。從mod3.py中引用的話也就是sub_pkg2這一層。
..結果為父包(pkg),../sub_pkg1結果為子包sub_pkg1。
pkg/sub_pkg2/mod3.py
def baz(): print('[mod3] baz()') class Baz: pass from .. import sub_pkg1
print(sub_pkg1) from ..sub_pkg1.mod1 import foo
foo()
>>> from pkg.sub_pkg2 import mod3
<module 'pkg.sub_pkg1' (namespace)>
[mod1] foo()
數據分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
CDA數據分析師證書考試體系(更新于2025年05月22日)
2025-05-26解碼數據基因:從數字敏感度到邏輯思維 每當看到超市貨架上商品的排列變化,你是否會聯想到背后的銷售數據波動?三年前在零售行 ...
2025-05-23在本文中,我們將探討 AI 為何能夠加速數據分析、如何在每個步驟中實現數據分析自動化以及使用哪些工具。 數據分析中的AI是什么 ...
2025-05-20當數據遇見人生:我的第一個分析項目 記得三年前接手第一個數據分析項目時,我面對Excel里密密麻麻的銷售數據手足無措。那些跳動 ...
2025-05-20在數字化運營的時代,企業每天都在產生海量數據:用戶點擊行為、商品銷售記錄、廣告投放反饋…… 這些數據就像散落的拼圖,而相 ...
2025-05-19在當今數字化營銷時代,小紅書作為國內領先的社交電商平臺,其銷售數據蘊含著巨大的商業價值。通過對小紅書銷售數據的深入分析, ...
2025-05-16Excel作為最常用的數據分析工具,有沒有什么工具可以幫助我們快速地使用excel表格,只要輕松幾步甚至輸入幾項指令就能搞定呢? ...
2025-05-15數據,如同無形的燃料,驅動著現代社會的運轉。從全球互聯網用戶每天產生的2.5億TB數據,到制造業的傳感器、金融交易 ...
2025-05-15大數據是什么_數據分析師培訓 其實,現在的大數據指的并不僅僅是海量數據,更準確而言是對大數據分析的方法。傳統的數 ...
2025-05-14CDA持證人簡介: 萬木,CDA L1持證人,某電商中廠BI工程師 ,5年數據經驗1年BI內訓師,高級數據分析師,擁有豐富的行業經驗。 ...
2025-05-13CDA持證人簡介: 王明月 ,CDA 數據分析師二級持證人,2年數據產品工作經驗,管理學博士在讀。 學習入口:https://edu.cda.cn/g ...
2025-05-12CDA持證人簡介: 楊貞璽 ,CDA一級持證人,鄭州大學情報學碩士研究生,某上市公司數據分析師。 學習入口:https://edu.cda.cn/g ...
2025-05-09CDA持證人簡介 程靖 CDA會員大咖,暢銷書《小白學產品》作者,13年頂級互聯網公司產品經理相關經驗,曾在百度、美團、阿里等 ...
2025-05-07相信很多做數據分析的小伙伴,都接到過一些高階的數據分析需求,實現的過程需要用到一些數據獲取,數據清洗轉換,建模方法等,這 ...
2025-05-06以下的文章內容來源于劉靜老師的專欄,如果您想閱讀專欄《10大業務分析模型突破業務瓶頸》,點擊下方鏈接 https://edu.cda.cn/g ...
2025-04-30CDA持證人簡介: 邱立峰 CDA 數據分析師二級持證人,數字化轉型專家,數據治理專家,高級數據分析師,擁有豐富的行業經驗。 ...
2025-04-29CDA持證人簡介: 程靖 CDA會員大咖,暢銷書《小白學產品》作者,13年頂級互聯網公司產品經理相關經驗,曾在百度,美團,阿里等 ...
2025-04-28CDA持證人簡介: 居瑜 ,CDA一級持證人國企財務經理,13年財務管理運營經驗,在數據分析就業和實踐經驗方面有著豐富的積累和經 ...
2025-04-27數據分析在當今信息時代發揮著重要作用。單因素方差分析(One-Way ANOVA)是一種關鍵的統計方法,用于比較三個或更多獨立樣本組 ...
2025-04-25CDA持證人簡介: 居瑜 ,CDA一級持證人國企財務經理,13年財務管理運營經驗,在數據分析就業和實踐經驗方面有著豐富的積累和經 ...
2025-04-25