Python第十章-模塊和包

模塊和包

我們以前的代碼都是寫在一個文件中, 而且代碼也比較短.

  1. 假設我們現在要寫一個大的系統, 不可能把代碼只寫到一個文件中, 迫切想把代碼寫到不同的文件中, 並且能夠在一個文件使用另一個文件中代碼. 這個時候應該怎麼做?
  2. 如果你有一個非常棒的函數, 想在多個程序中使用, 又不想 copy, 這個時候又應該怎麼做?

為了解決上面的這些問題, python 提出了一個moudle的概念.

我們每定義一個.py文件, 其實就是定義了一個moudle.在一個moudel中定義的函數,類都可以導入(import)到另外一個模塊中, 也可以導入到主模塊(main moudle)中.


一個文件就是一個模塊, 在一個模塊內可以定義變量, 函數, 類等, 也可以有合法的 python 語句.

文件名就是模塊名(不包括擴展名.py). 我們可以通過一個全局變量__name__來獲取這個模塊的名字, 當然獲取到的是個字符串.

一、模塊基本使用

1.1 定義模塊

定義模塊其實就是創建一個.py文件.
python之父建議的的模塊命名:

  1. 公共模塊: 所有的字母小寫, 不同的單詞之間用下劃線_連接
  2. 內部模塊: 用下劃線_開頭, 其餘與公共模塊一樣.

定義一個計算斐波那契數列前 n 項的模塊

第 1 步: 創建一個文件fibo.py, 相當於創建了一個模塊fibo.

def fib(n):      #1,1,2,3,5,8,13,21,34...      a, b = 0, 1      for i in range(n):          print(b, end=" ")          a, b = b, a + b      #print()  

第 2 部: 再創建一個模塊demo, 作為我們程序的入口, 這樣的模塊其實就是main moudle. 在主模塊內部使用我們第 1 步創建的模塊.

import fibo  # 導入需要的模塊    print(__name__) # 當前模塊直接執行, 所以當前模塊就是主模塊 輸出__main__    # 模塊名就成為了在 fibo 這個模塊中定義的全局變量, 函數, 類的命名空間    print(fibo.__name__)    # 輸出模塊名    # 調用模塊內定義的函數    fibo.fib(20)  


1.2 模塊的細節

  1. **使用import加載模塊的位置. **
    可以放置文件的任何位置, 但是建議放置文件的開始位置來加載模塊. 而且一定要保證在使用模塊前先加載模塊
  2. 首次導入模塊, 會創建一個以模塊名命名的命名空間. 在這個模塊中定義的全局變量, 函數, 類都處於這個命名空間下
  3. 在新創建的命名空間中, 執行模塊內的代碼. 如果有全局變量, 函數, 類啥的都會完成定義工作. 如果有輸出, 你也會立即看到結果.
  4. 使用模塊名.成員的方式來使用模塊內定義的成員:全局變量, 函數, 類
  5. 模塊內定義的類的使用要注意也是模塊名.類名的方式來使用.比如模塊a中定義了一個類Person, 則創建對象的方式:p = a.Person()

二、import語句進一步講解

2.1 給導入的模塊起別名

導入模塊之後, 可能模塊名比較長, 想換個短點的, 我們可以給導入的模塊起個別名.

import hello_world as hw    hw.foo()  

注意:

起別命名之後只會創建hw命名空間, 而不會再創建hello_world命名空間. 所以這樣使用是錯誤的:hello_world.foo()

2.2 一次導入多個模塊

前面我們已經了解到使用import可以導入一個模塊.

我們也可以多次使用import來導入多個模塊

import a  import b  import c  

上面這種寫法比較啰嗦, 可以使用一個import同時導入多個模塊

import a, b, c  

2.3 從模塊導入具體的定義(from方式導入)

單獨使用import是導入整個模塊, 默認的所有定義都會導入, 而且會創建新的命名空間. 並沒模塊的代碼也會執行.

如果我們僅僅是用到模塊中某個函數或者類, 這個時候, 我們可以只導入我們想要的某個定義, 而不需要導入整個模塊.

語法:

from 模塊 import 具體的定義  
from fibo import fib    # 從模塊 fibo 中只導入 fib    fib(20)  

說明:

  1. 通過from語句導入的時候, 並不會創建新的命名空間, 而是把導入的定義放在了當前命名空間中, 所以使用的時候不需要添加命名空間.
  2. 這種寫法是錯誤的:fibo.fib(20), 因為根本就不存在fibo這個命名空間
  3. 可以一次導入多個定義:from fibo import fib, a, b, c
  4. 使用通配符導入一個模塊中所有的定義:from fibo import *

from導入的作用域問題


把一個函數從一個模塊導入當前模塊時, 並不會改變這個函數的作用域規則. 也就是說好函數的全局命名空間仍然是那個函數定義所在的命名空間.

a.py:

n = 30      def foo():      print("a模塊: n的值" + str(n))  

b.py:

from a import foo, n    n = 1000  foo()  print("當前模塊: n 的值:" + str(n))  #print("當前模塊:n的值:",n)  #print("當前模塊:n的值:%d"%n)  

原因分析:

foo函數中的n, 仍然是a模塊中的n

為什麼呢?

在 python 中任何數據都是變量, n僅僅是對象的一個引用(符號). 導入 n 到當前模塊, 僅僅是在原來的對象上多了一個引用而已.

這個兩個n由於命名空間不同, 所以不是同一個n. 修改一個變量的值, 其實是讓這個變量指向了一個新的對象

導入成功from a import foo, n之後是這樣的:

在 b 模塊中修改 n 的值n = 1000 之後是這樣的:

三、模塊運行方式

對模塊的運行方式做個總結.

到目前為止, 我們有兩種方式去運行模塊:

  1. 直接使用python 模塊名.py. 這種模塊為主模塊, 這樣運行方式叫做以程序的方式運行.
  2. 使用 import語句的方式來運行模塊.

我們可以通過__name__屬性的值來判斷這個模塊的運行方式.

if __name__ == "__main__":      # 是 程序的方式運行      pass  else:      # 否 以導入的方式運行      pass  

四、模塊搜索路徑

當我們去加載一個模塊的時候, python 會在指定的路徑中搜索這個模塊, 一旦搜索到則會立即導入. 如果搜索不到則會拋出異常.

出現下面這種情況一般是模塊的路徑不對導致的!

4.1 模塊的默認搜索路徑

python 搜索模塊按照一定的順序來搜索的:

  1. 當前目錄
  2. 系統內置模塊
  3. 安裝的第三方模塊

搜索路徑存放在syspath變量中.


4.2更改模塊的搜索路徑

兩種更方式:

  1. 動態更改
  2. 持久更改

動態更改

sys.path是一個列表, 只需要在裏面添加上你需要的路徑即可.

sys.path.append("路徑")  

持久更改

設置環境變量PYTHONPATH, 在這個環境變量中設置你需要的路徑即可. 只添加自己的路徑即可, python 自己本身的搜索路徑不受影響

五、包(package)

5.1 包概述

一個.py文件就是一個模塊, 但是模塊一多, 管理起來也是比較麻煩.

python 提供包是為了更好的對模塊進行管理.

包可以看做是一組模塊的集合, 把這組模塊放在一個包的名稱下.包這項技術可以解決不同的應用程序之間模塊的命名衝突問題.


包包括兩個必要條件:

  1. **一個文件夾. **
    這個文件夾的名字其實就是包的名字.包的命名規則和公共文件的命名一致:全部字符小寫, 不同的單詞之間用空格隔開.
  2. 在前面的文件夾下的一個__init__.py的初始化文件.
    這個文件可以為空, 也可以定義一些導入模塊時候的初始化代碼.

注意:

一個包中可以有自己的子包, 子包也可以再有子包… 每一個包都會有一個自己的__init__.py文件.


5.2 從包中導入模塊

定義包是為了更好的管理模塊, 有幾種導入模塊的方式!

假設有這樣的一個包結構:


使用import導入模塊

# 導入 echo 模塊  import sound.effects.echo    # 使用的時候必須使用全名  sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)  

使用from導入模塊

# 導入 echo 模塊  from sound.effects import echo    # 只需要包名即可  echo.echofilter(input, output, delay=0.7, atten=4)  

使用from直接導入模塊中的定義

# 從模塊中導入定義  from sound.effects.echo import echofilter    # 直接調用導入的函數即可  echofilter(input, output, delay=0.7, atten=4)  

注意:

當我們多個模塊中都導入同一個模塊的時候, 只有第一次導入的時候才會執行包的__init__()和模塊中的代碼.

可以這樣理解: 當導入某個模塊的時候, 會先查找某個模塊是否已經導過, 如果已經導入過, 就直接使用不再重新導入.

使用*通配符導入

使用from 包 import *, 可以導入整個包下所有的模塊.

但是由於各個操作系統的在對文件名的命名上的差異, 默認情況下這個語句一個包都導入不了.

我們可以在這個包的__init__.py定義一個列表, 這個列表中定義使用*的時候可以導入哪些包

__all__ = ["a", "b"]  # 使用 * 的時候導入a 和 b 兩個包  

5.3使用相對導入

同一個包下的模塊互相導入的時候, 也需要些完全限定名, 也就是把他們的上級的所有的包都要寫上, 這是比較麻煩的.

所以, python 還是支持一種相對導入.

一個點.表示當前包, 兩個點..表示表示父包

相對導包只能用在from中.

from . import my2  

注意:

相對導包的原理是根據當前模塊的__name__來計算的包的路徑, 由於主模塊的__name__值永遠是__main__, 所以主模塊中不能使用相對導包, 只能使用絕對路徑去導包.

有了包之後, 模塊都應該放入到相應的包下