搞懂Python包之後,感覺學Python輕鬆多了

2021-03-02 麥叔編程
1 前言

學習方法不對,事倍功半!學習方法對了,事半功倍。

學編程,要先紮實的學好基礎語法和結構,剩下的就是不斷的實戰應用,同時按需加強相關知識。

Python的包就是這裡說的基礎語法結構之一。

把手放在胸口上,問問自己,你對Python包的了解有多少?然後認真看完本文。你的今天一定是有進步的。

包是基於模塊的,是對模塊的組織,建議和另一篇模塊文章一起看,融會貫通起來。模塊文章連結見文末往期推薦第1篇

2 Python包

假設你已經開發了一個包含許多模塊的非常大的應用程式。隨著模塊數量的增長,如果將它們都放到一個位置,則很難跟蹤所有模塊。如果它們有相似的名稱或功能,情況會更糟。你可能希望把他們放在不同的文件夾中,這就是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
<pkg.mod2.Bar object at 0x033F7290>

from <module_name> import <name(s)>

>>> from pkg.mod1 import foo
>>> foo()
[mod1] foo()

from <module_name> import <name> as <alt_name>

>>> from pkg.mod2 import Bar as Qux
>>> x = Qux()
>>> x
<pkg.mod2.Bar object at 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 "<pyshell#34>", line 1, in <module>
    pkg.mod1
AttributeError: module 'pkg' has no attribute 'mod1'
>>> pkg.mod1.foo()
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    pkg.mod1.foo()
AttributeError: module 'pkg' has no attribute 'mod1'
>>> pkg.mod2.Bar()
Traceback (most recent call last):
  File "<pyshell#36>", line 1, in <module>
    pkg.mod2.Bar()
AttributeError: module 'pkg' has no attribute 'mod2'

要實際導入模塊或其內容,需要使用上面展示的import例子。

3 包初始化

如果一個名為__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文件的包。當然,如果需要包初始化,它仍然可以存在。但現在不再是必須的了。

4 Importing * From a Package

為了以下討論的目的,先前定義的包被擴展以包含一些額外的模塊:

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 <package_name> 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 "<pyshell#37>", line 1, in <module>
    Foo
NameError: name 'Foo' is not defined

foo()(函數)現在定義在本地命名空間中,但foo(類)沒有定義,因為後者不在__all__中。

總之,當import *被指定時,__all__會被包和模塊用來控制導入的內容。但是默認行為是不同的:

對於一個包:當__all__沒有定義,import *不導入任何東西。對於一個模塊:當__all__沒有定義,import *導入所有內容(除了以下劃線開頭的名稱)。

5 子包

包可以包含任意深度的嵌套子包。例如,讓我們對示例包目錄再做一個修改,如下所示:

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()

6 麥叔廣告時間

掌握好的學習方法會加快學習速度,更好的方法是有個資深又負責的老司機帶你

歡迎加入麥叔私教訓練營,趁著春節和寒假,投資自己,讓自己更快更早的進步,就是送給自己最好的禮物!讓自己有一個大的進步,更好的迎接新的一年。

在私教班中,我總結了學Python必備的36個技能和大量的實戰應用,一個月之內就能在工作中實際應用,快的一兩周就可以。有興趣加maishu1024,消息註明私教

7 點讚是美德

今天是乾貨系列,所以特別需要你的點讚在看。謝謝啦!🙏

大家都喜歡花邊新聞,乾貨的閱讀量一般都比較慘,既然你都翻到最後了,說明是個喜歡技術的人,給我點個在看吧。

相關焦點

  • python入門書籍,輕鬆學python
    全民學python的熱潮已經開啟,然而,對於這種情況,還是有很多小夥伴私信我python到底該怎麼入門?沒接觸過編程能學會嗎?
  • 學Python有前途嗎?學Python能做什麼?
    學Python能做什麼?今天優就業小編就詳細為大家解答一下以上問題。1、學習完Python之後能做什麼呢?近年來,Python因為簡單易學、功能強大成為最近大火的程式語言,大家在學習的過程中,也不可避免會想到學習完Python之後能做些什麼呢?Python語言能做到的東西還是超級多的,像大家都比較熟悉的爬蟲、web應用開發、人工智慧、數據分析等等,Python都可以輕鬆完成。
  • 從零開始學量化(二):python/matlab/r/sas/vba選哪個
    我個人來說,最開始是大二做數學建模開始學matlab,不過現在來看其實建模用python也挺好,不明白為什麼當時所有的人都會推薦matlab,可能已經是一種傳統了吧。之後大三上統計課學習了R,上計量課學習了stata,之後大四研究生實習又學了python,vba。整體就是這樣,接下來分軟體說說自己的體會。
  • python基礎----python編碼風格、包
    編寫易於理解的乾淨整潔的代碼也是非常重要的,即使是在編寫數周之後希望也是能夠便於理解的。NumPy 模塊允許使用比嵌套列表的原生 Python 解決方案快得多的多維數組。它還包含執行數學運算的函數,例如對數組進行矩陣變換。庫 SciPy 包含對 NumPy 功能的大量擴展。Python 也可用於遊戲開發。
  • 【Python金融量化】零基礎如何開始學?
    來源:Python金融量化   連結:https://mp.weixin.qq.com/s/_buUzztomEWFqgD8gajJuwPython可以說是當前非常流行的程式語言,甚至有點「網紅」的感覺
  • 怎麼學python
    資源一: 1          Python官網文檔:連結:https://docs.python.org/2/tutorial/ 2          Google Python教程:連結:https://developers.google.com/edu/python/ 3          菜鳥教程:連結:https://docs.python.org/2/tutorial/https://www.runoob.com
  • 【一起學python】print 語句
    聯盟有個小夥伴,為了督促自己學習進步,決定把自己以前學的python重新梳理下,並且以文章的方式展示出來,聯盟專門做一起學python
  • 為什麼說學人工智慧一定要學Python?很多人都不知道Python的強大
    怎麼學習學習高等數學基礎知識首先,你是零基礎的話,就先將高等數學基礎知識學透,從基礎的數據分析、線性代數及矩陣等等入門,只有基礎有了,才會層層積累,不能沒有邏輯性的看一塊學一塊。具體學習內容請看圖。學習PythonPython具有豐富和強大的庫。它常被暱稱為膠水語言,能夠把用其他語言製作的各種模塊(尤其是C/C++)很輕鬆地聯結在一起。
  • Python練習題100題-帶你輕鬆入門Python
    近日發現一個Python入門的練習倉庫,作者收集100多道Python的常見練習題,幾乎概括了Python初學要掌握的基本問題。
  • 使用conda管理python包
    作為一款管理python安裝包的包管理器,其功能要比python自帶的pip強大不少。安裝好anaconda時會默認安裝conda,以及一些python安裝包。然後可以根據個人需要,使用conda安裝其餘的第三方包,conda會自動解決包之間的依賴關係。在安裝第三方包時,由於網絡連接原因,連接默認源的速度會很慢,有時會出現連接中斷,甚至無法連接的情況。
  • Python的多進程
    Python的os模塊封裝了常見的系統調用,其中就包括fork,可以在Python程序中輕鬆創建子進程:import osprint('Process (%s) start...' % os.getpid())pid = os.fork()if pid == 0: print('I am child process
  • 在Windows上通過pip安裝Python軟體包
    介紹1.1 介紹python是一個可以做很多事情的語言,之所以可以做很多事情是因為python有非常多的軟體包,不同的功能需要使用不同的軟體包,python自帶了一個軟體包管理功能——pip,我們管理python的軟體包就可以用pip這個工具了。要學習python就必須學會軟體包的安裝、升級、卸載等等操作,下面童鞋們就跟著福哥來學習pip的使用方法吧。2.
  • 為什麼你應該學 Python ?
    現在你也許會想我將會在哪裡說服你學 Python 是個好主意,不要擔心,馬上就到。作為引言的結尾,我想說明,這只是我對這個語言的個人感受,只是個人偏好。我沒有試圖以「如果你用 Python,你就不是真正的程式設計師(實際上,我不這麼認為)」的理由勸說人們學 C。當有人問我他們的入門語言應該選哪個,我通常建議他們選 Python,基於我上邊提到的「缺點」的原因。
  • Python每日一談|No.14.模塊(包)的使用
    本來打算寫類的,但是想了下,寫一個類然後打包發布,對於使用者來說難度有點大所以我們就簡單介紹一下包的使用和安裝,足夠大家使用就好
  • Stata+Python-3:Stata中下載安裝Python包
    但是Python的真正強大之處在於成千上萬的免費軟體包。今天,我想向您展示如何下載和安裝Python包。1、使用pip安裝Python包讓我們 先輸入python query來驗證是否在我們的系統上安裝了python,以及Stata是否設置為使用python。
  • Python到底是個啥?為什麼這麼多人都要學?
    Hello,大家好,我是橘子呀~從今天開始跟大家一起學習Python,之後會不定期更新Python的相關文章。言歸正傳,今天我想跟大家分享一下python是什麼以及學習python對你有什麼幫助。一定要耐心看完喲~ 或許對現在的你有一定的啟發。
  • 快速打造多版本Python環境
    7 分鐘。配置環境:CentOS release 6.8pyenv 20160509在工作開發中,一直使用 virtualenv 來管理python的包環境。很好的解決了不同項目使用不同python包的需求。
  • 【創課堂】——笨辦法學Python(0)
    到處找找,你會找到的。4. 把 Terminal 也放到 Dock 裡面。5. 運行 Terminal 程序,這個程序看上去不怎麼地。6. 在 Terminal 程序裡邊運行 python。 運行的方法是輸入程序的名字再敲一下回車。7. 敲擊 CTRL-D (^D) 退出 python。8. 這樣你就應該退回到敲 python 前的提示界面了。
  • 聽說一個LabVIEW的朋友轉行學了Python
    我說,你百度下網絡爬蟲,資料那麼多,不用來問我吧。過了幾天,她跟我說已經學會了爬蟲編程。我心中暗自笑了笑,問了下,是不是用了python。畢竟一搜資料,都是python的資料。她說是。又有一天,和以前老朋友聊天,當然通過LabVIEW編程網上認識的,說現在不怎麼整LabVIEW了,在搞python,也挺方便。還說了那一句:「人生苦短,快用pyhton」。
  • Python基礎篇 - 11 Linux(Ubuntu)系統安裝Python
    還有很多不知道學Python能幹什麼?在這裡我會為大家一一分享,我感覺還是很有意思的。雖然對於那些會的人不難,但是對於小白來說這確實是很好的一種學習思路。圖 2 找到源碼包地址在「Gzipped source tarball」處單擊滑鼠右鍵,從彈出菜單中選擇「複製連結地址」,即可得到.tgz格式的源碼壓縮包地址。