Python學習筆記5:模組/包

1.模組
模組簡單理解就是一組功能的集合。
在Python中,一個文件(.py)就是一個模組,文件名即模組名。
模組的好處是大大提高程式碼的可維護性,其次,程式碼不必從零開始。當一個模組編寫完畢,就可以被其他地方引用。

模組可以包含可執行的語句和函數的定義,這些語句的目的是初始化模組,它們只在模組名第一次遇到導入import語句時才執行(import語句是可以在程式中的任意位置使用的,且針對同一個模組很import多次,為了防止你重複導入,python的優化手段是:第一次導入後就將模組名載入到記憶體了,後續的import語句僅是對已經載入大記憶體中的模組對象增加了一次引用,不會重新執行模組內的語句)

#!/usr/bin/python3
# -*- coding: utf-8 -*-

#定義名為learn.py的模組
print('beginning learn module')
modulename = 'learn'

def fun1():
    print('hello fun1')

def fun2():
    print('hello fun2')

print('end learn module')

#在另外一個文件中導入這個模組
import learn
import learn
#列印輸出
beginning learn module
end learn module
#可以發現只列印了一次,是因為每次導入模組時,解釋器都回去檢查一下這個模組有沒有之前被導過。

模組導入
首先解析器是找到這個模組,然後判斷模組是否被導入過。
如果沒有導入,則:
創建一個屬於這個模組的命名空間;如果用戶沒有定義變數來引用這個模組的記憶體地址的話,那麼就使用模組的名稱來引用這個模組的記憶體地址;如果用戶使用as來指定變數接受這個記憶體地址的話,那麼就將記憶體地址賦值給這個變數;且下文在調用時只能使用這個變數進行調用不能再使用模組名進行調用了。然後執行這個模組中的程式碼;
如果已經導入,則:
解釋器不會重新執行模組內的語句,後續的import語句僅僅是對已經載入到記憶體中的模組的對象增加一次引用;

import module
#或
import module1, module2, module3

模組別名
模組在導入的時候開闢了新空間記憶體,默認是使用模組的名稱來引用這個記憶體地址的,有時候模組的名稱很長再加上執行調用裡面的功能的時候,就顯的很不方便,為了更好的使用模組,我們可以給模組起別名;
也就是在導入模組的時候我們不讓它使用默認的名字來引用記憶體地址,而是由我們自己定義的變數來引用這個模組的記憶體地址;

import module as m

這樣的話就表示使用變數m來引用這個記憶體地址,然後我們在文中再使用這個模組的時候,只要使用m來調用這個模組里的功能即可。

導入多個模組

import os,time,sys,re   #每個模組之間用逗號隔開
#或
import os
import time
import sys

規範建議:模組應該一個一個的導入,先後順序為:內置模組—->擴展(第三方)模組——>自定義模組;
順序說明:我們知道導入模組其實就是在執行這個模組,我們不確定擴展模組或自定義模組里有沒有某個功能調用了內置模組,所以,我們在導入模組時,應先導入解釋器內置的模組,然後在導入擴展模組,最後導入自定義模組。

from…..import
在使用一個模組時往往我們只需要使用其中某一個或者幾個功能,如果直接用import module則會將模組全部導入。
比如上面的learn.py模組中我只想使用fun1函數功能,則可以這樣導入:

from learn import fun1

from…..import 也支援as模式,也支援導入某個模組的多個功能:

from learn import fun1 as f1
from learn import fun1 ,fun2

from…..import *
表示導入模組中所有的不是以下劃線(_)開頭的名字都導入到當前位置,大部分情況下我們的python程式不應該使用這種導入方式,因為你不知道你導入什麼名字,很有可能會覆蓋掉你之前已經定義的名字。而且可讀性極其的差,在互動式環境中導入時沒有問題。
還有一點要說的是,如果使用* 的方式進行了導入,這時只想使用裡面的某個或某些功能時,可以使用__all__來進行約束;
注意:__all__只是用來約束*方式的,其他方式導入的話,不會生效;具體使用見例子:

#定義名為learn.py的模組
print('beginning learn module')
modulename = 'learn'

def fun1():
    print('hello fun1')

def fun2():
    print('hello fun2')

print('end learn module')

#導入調用
from learn import *

fun1()    
print(modulename)
fun2()

#輸出如下:
beginning learn module
end learn module
hello fun1
learn
hello fun2

現在對其進行約束一下,要求只能使用fun1()功能;
我們在learn模組開頭加入一下

__all__ = ["func1"]

#運行輸出:
beginning learn module
end learn module
hello fun1
Exception has occurred: NameError
name 'modulename' is not defined
  File "D:\zPY\test\test_learn.py", line 9, in <module>
    print(modulename)

作用域
在一個模組中,可能會定義很多函數和變數,但有的函數和變數我們希望給別人使用,有的函數和變數我們希望僅僅在模組內部使用。在Python中,是通過_前綴來實現的。
正常的函數和變數名是公開的(public),可以被直接引用,比如:abc,x123,PI等;
類似__xxx__這樣的變數是特殊變數,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊變數,hello模組定義的文檔注釋也可以用特殊變數__doc__訪問,我們自己的變數一般不要用這種變數名;
類似_xxx和__xxx這樣的函數或變數就是非公開的(private),不應該被直接引用,比如_abc,__abc等;
之所以我們說,private函數和變數「不應該」被直接引用,而不是「不能」被直接引用,是因為Python並沒有一種方法可以完全限制訪問private函數或變數,但是,從編程習慣上不應該引用private函數或變數。

2.包
包是一種管理Python模組命名空間的形式,包是一個有層次的文件目錄結構,由模組和子包組成。通俗的講,包就是(一定)包含__init__.py文件的文件夾。

package_a
├── __init__.py
├── module_a1.py
└── module_a2.py

包導入
在導入一個包的時候,Python 會根據 sys.path 中的目錄來尋找這個包中包含的子目錄。
目錄只有包含一個叫做 init.py 的文件才會被認作是一個包,主要是為了避免一些濫俗的名字(比如叫做 string)不小心的影響搜索路徑中的有效模組。
最簡單的情況,放一個空的 :file:init.py就可以了。

import package_a.module_a1

跟之前的模組導入一樣,在__init__.py文件中可以定義__all__變數來控制from package import *的導入,如下:

__all__ = ["fun1", "fun2"]

這種情況下from package import *只能導入fun1和fun2函數,其他的都不能使用。

注意:
當使用 from package import item 這種形式的時候,對應的 item 既可以是包裡面的子模組(子包),或者包裡面定義的其他名稱,比如函數,類或者變數。
import 語法會首先把 item 當作一個包定義的名稱,如果沒找到,再試圖按照一個模組去導入。如果還沒找到,拋出一個 :exc:ImportError 異常。
反之,如果使用形如 import item.subitem.subsubitem這種導入形式,除了最後一項,都必須是包,而最後一項則可以是模組或者是包,但是不可以是類,函數或者變數的名字。

Tags: