1. 程式人生 > >模組和模組的匯入 包和包的匯入

模組和模組的匯入 包和包的匯入

 

一. 模組  

  首先,我們先看一個老⽣常談的問題. 什麼是模組. 模組就是一個包含了python定義和聲明的檔案, 檔名就是模組的名字加上.py字尾. 換句話說我們目前寫的所有的py檔案都可以看成是⼀個模組但是我們import載入的模組一共分成四個通⽤類別:

  1. 使用pyhton編寫的py檔案
  2. 已被變異為共享庫或者DLL或C或者C++的擴充套件
  3. 包好一組模組的包.
  4. 使用c編寫並連線到python直譯器的內建模組  

  為什麼要使用模組? 為了我們寫的程式碼可以重用. 不至於把所有的程式碼都寫在一個檔案內. 當專案規模比較小的時候. 完全可以使⽤一個py搞定整個專案的開發. 但是如果是一個非

常龐大的專案. 此時就必須要把相關的功能進行分離. 方便我們的日常維護. 以及新專案的開發.

如何使用模組? 我們已經用過很多模組了. 匯入模組有兩種方式

  1. import 模組


  2. from xxx import xxxx

 

二. import


  首先. 我們先看import, 在使⽤用import的時候, 我們先建立一個yitian.py. 在該檔案中建立
一些武林前輩和一些打鬥場景, 程式碼如下.

print("⽚頭曲. 啊! 啊~ 啊! 啊. 啊啊啊啊啊啊啊...")
main_person_man = "張無忌"
main_person_woman 
= "趙敏" low_person_man_one = "成昆" low_person_man_two = "周芷若"

def fight_on_light_top():   print("光明頂⼤大戰", main_person_man, "破壞了", low_person_man_one, "的⼤大陰") def fight_in_shaolin():   print("少林林寺⼤大戰", main_person_man, "破壞了", low_person_man_two, "的⼤大陰")

  接下來, ⾦庸上場.

import yitian
print
(yitian.main_person_man) # 使⽤用模組中定義好的名字 print(yitian.low_person_man_one)
yitian.fight_in_shaolin()
# 調⽤用模組中的函式 yitian.fight_on_light_top()

此時我們在⾦庸模組中引入了yitian模組.

  在Python中模組是不能夠重複匯入的. 當重複匯入模組時. 系統會根據sys.modules來判
斷該模組是否已經匯入了. 如果已經匯入. 則不會重複匯入

import sys
print(sys.modules.keys()) # 檢視匯入的模組.
import yitian # 導⼊模組. 此時會預設執行該模組中的程式碼
import yitian # 該模組已經導⼊過了. 不會重複執行程式碼
import yitian
import yitian
import yitian
import yitian

  

# 匯入模組的時候:
# 1. 去判斷當前正在匯入的模組是否已經倒入過
# 2. 如果已經匯入過,不會重新匯入該模組
# 3. 如果沒有匯入過. 首先開闢一個記憶體空間
# 4. 把該模組中的程式碼放在新開闢的空間中. 執行該模組中的程式碼
# 5. 把該檔案的名字作為當前名稱空間的名字(前提是沒有as)

 匯入模組的時候都做了些什麼? 首先. 在匯入模組的一瞬間. python直譯器會先通過
sys.modules來判斷該模組是否已經匯入了該模組. 如果已經匯入了則不再匯入. 如果該模組
還未匯入過. 則系統會做三件事.


  1. 為匯入的模組創立新的名稱空間


  2. 在新建立的名稱空間中運行該模組中的程式碼


  3. 建立模組的名字. 並使⽤該名稱作為該模組在當前模組中引用的名字.
我們可以使用globals來檢視模組的名稱空間

print(globals())
列印結果:
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <_frozen_importlib_external.SourceFileLoader object at
0x10bbcf438>, '__spec__': None, '__annotations__': {}, '__builtins__':
<module 'builtins' (built-in)>, '__file__':
'/Users/sylar/PycharmProjects/oldboy/模組/模組/⾦庸.py', '__cached__': None,
'yitian': <module 'yitian' from '/Users/sylar/PycharmProjects/oldboy/模組/模
塊/yitian.py'>, 'sys': <module 'sys' (built-in)>} 

  注意. 由於模組在匯入的時候會建立其⾃己的名稱空間. 所以. 我們在使用模組中的變量
的時候一般是不會產生衝突的.

import yitian
main_person_man = "胡一菲"
def fight_in_shaolin():
  print(main_person_man, "⼤戰曾小賢")

print(yitian.main_person_man) # 張無忌 print(main_person_man) # 胡一菲 yitian.fight_in_shaolin() # 倚天屠龍記中的 fight_in_shaolin() # ⾃己的

  注意. 在模組中使用global. 我們之前說global表示把全域性的內容引入到區域性. 但是. 這個
全域性指的是py⽂件. 換句話說. global指向的是模組內部. 並不會改變外部模組的內容

模組 yitian 中:
print("⽚頭曲. 啊! 啊~ 啊! 啊. 啊啊啊啊啊啊啊...")
main_person_man = "張⽆忌"
main_person_woman = "趙敏敏"
low_person_man_one = "成昆"
low_person_man_two = "周芷若"

def fight_on_light_top():   print("光明頂大戰", main_person_man, "破壞了", low_person_man_one, "的⼤陰") def fight_in_shaolin():   global low_person_man_two # 注意看, 此時的global是當前模組. 並不不會影響 其他模組   low_person_man_two = "戰五渣"   print("少林寺大戰", main_person_man, "破壞了", low_person_man_two, "的大陰") 調⽤方: import yitian low_person_man_two = "劉海柱" yitian.fight_in_shaolin() print(yitian.low_person_man_two) # 戰五渣 print(low_person_man_two) # 劉海柱. 並沒有改變當前模組中的內容. 所以模組內部的 global只是用於模組內部

  特別特別要注意. 如果我們在不同的模組中引入了同一個模組. 並且在某一個模組中改
變了被引入模組中的全域性變量. 則其他模組看到的值也跟著邊. 原因是python的模組只會引入
一次. 大家共享同⼀個名稱空間

金庸:
import yitian
yitian.main_person_man = "滅絕師太"

金庸二號: import yitian import 金庸 print(yitian.main_person_man) # 滅絕師太.


  

  上述問題出現的原因:    
  1. 大家共享同一個模組的名稱空間.
    
  2. 在金庸里改變了主角的名字
  
如何解決呢?  
⾸先. 我們不能去改python. 因為python的規則不是我們定的.
只能想辦法不要改變主
角的名字. 但是. 在金庸里我就有這樣的需求. 那此時就出現了.
在金庸被執行的時候要執行
的程式碼. 在金庸被別⼈匯入的時候我們不想執行這些程式碼.
此時, 我們就要利⽤一下__name__
這個內建變量了. 在Python中.
每個模組都有⾃己的__name__ 但是這個__name__的值是不
定的. 當我們把一個模組
作為程式運行的入口時. 此時該模組的__name__是"__main__" , 而

如果我們把模組匯入時. 此時模組內部的__name__就是該模組⾃身的名字

金庸:
print(__name__)
# 此時如果運⾏該⽂件. 則__name__是__main__
⾦庸二號:
import 金庸
#此時列印的結果是"金庸"

  我們可以利用這個特性來控制模組內哪些程式碼是在被載入的時候就運行的. 哪些是在模
塊被別⼈匯入的時候就要執行的. 也可以遮蔽掉一些不希望別人匯入就運行的程式碼. 尤其是測
試程式碼.

if __name__ == '__main__':
  yitian.main_person_man = "滅絕師太" # 此時, 只有從該模組作為⼊口運行的時候才
                        會把main_person_man設定成滅絕師太
  
print("哇哈哈哈哈哈") # 只有運行該模組才會列印. import的時候是不會執行這里的代                  碼的

我們還可以對匯入的模組進行重新命名:

import yitian as yt # 導⼊yitian. 但是名字被重新命名成了yt. 就好比變數賦值一樣.
a = 1 b = a
yt.fight_in_shaolin() # 此時可以正常運行
# yitian.fight_in_shaolin() # 此時程式報錯. 因為引入的yitian被重新命名成了yt
print(globals())
{
'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x103209438>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/sylar/PycharmProjects/oldboy/模組/模組/⾦金金庸.py', '__cached__': None, 'yt': <module 'yitian' from '/Users/sylar/PycharmProjects/oldboy/模組/模 塊/yitian.py'>}

一次可以引入多個模組

import time, random, json, yitian

  正確的匯入模組的順序:
    1. 所有的模組匯入都要寫在最上面. 這是最基本的
    2. 先引入內建模組
    3. 再引入擴充套件模組
    4. 最後引入你⾃己定義的模組

 

三. from xxx import xxx


  第⼀⼤塊關於import就說這麼多. 接下來. 我們來看from xxx import xxx這種匯入模組的
效果. 在使用from的時候, python也會給我們的模組建立名稱空間. 這一點和import是一樣
的. 但是from xxx import xxx的時候. 我們是把這個空間中的一些變數量引入過來了. 說⽩了. 就
是部分匯入. 當⼀個模組中的內容過多的時候. 我們可以選擇性的匯入要使用的內容.

 

from yitian import fight_in_shaolin
fight_in_shaolin()

  此時是可以正常運行的. 但是我們省略了之前的模組.函式() 直接函式()就可以執⾏了, 並且from語句也支援一行語句匯入多個內容.

 

from yitian import fight_in_shaolin, fight_on_light_top, main_person_man
fight_in_shaolin()
fight_on_light_top()
print(main_person_man)

   同樣支援as

from yitian import fight_in_shaolin, fight_on_light_top, main_person_man as
big_lao
fight_in_shaolin()
fight_on_light_top()
print(big_lao)

  

  最後. 看一下from的坑. 當我們從⼀個模組中引入一個變量的時候. 如果當前檔案中出現
了重名的量時. 會覆蓋掉模組引入的那個變量.

 

from yitian import main_person_man
main_person_man = "超級⼤大滅絕"
print(main_person_man)

  所以. 不要重名. 切記. 不要重名! 不僅是變數名不要重複. 我們⾃己建立的py⽂件的名字不要和系統內建的模組重名. 否則. 引入的模組都是python內建的模組. 切記, 切記.

 

 

 

 

 包和包的匯入


一. 知識點補充.


  我們現在知道可以使用import和from xxx import xxx來匯入一個模組中的內容. 那有一種
特殊的寫法: from xxx import * 我們說此時是把模組中的所有內容都匯入. 注意, 如果模組中
沒有寫出__all__ 則預設所有內容都匯入. 如果寫了__all__ 此時匯入的內容就是在__all__列表
中列出來的所有名字.

 

 

# haha.py
__all__ = ["money", "chi"]
money = 100
def chi():   print("我是吃") def he():   print("我是呵呵") # test.py

from haha import * chi()   print(money) # he() # 報錯

 

二. 包


  包是一種通過 '.模組名'來組織python模組名稱空間的方式. 那什麼樣的東西是包呢? 我
們建立的每個資料夾都可以被稱之為包. 但是我們要注意, 在python2中規定. 包內必須存在
__init__.py檔案. 建立包的目的不是為了運行, 而是被匯入使用. 包只是一種形式而已. 包的本
質就是一種模組
  為何要使用包? 包的本質就是一個資料夾, 那麼資料夾一的功能就是將檔案組織起來,
隨著功能越寫越多, 我們無法將所有功能都放在一個檔案中, 於是我們使用模組去組織功能,
隨著模組越來越多, 我們就需要用⽂件夾將模組檔案組織起來, 以此來提⾼程式的結構性和可
維護性
  

  首先, 我們先建立⼀些包. ⽤來作為接下來的學習. 包很好建立. 只要是一個資料夾, 有
__init__.py就可以.

 

import os
os.makedirs('glance/api')
os.makedirs('glance/cmd')
os.makedirs('glance/db')
l = []
l.append(open('glance/__init__.py','w'))
l.append(open('glance/api/__init__.py','w'))
l.append(open('glance/api/policy.py','w'))
l.append(open('glance/api/versions.py','w'))
l.append(open('glance/cmd/__init__.py','w'))
l.append(open('glance/cmd/manage.py','w'))
l.append(open('glance/db/__init__.py','w'))
l.append(open('glance/db/models.py','w'))
map(lambda f:f.close() ,l)
建立好目錄結構

只執行一次即可,就建立好了一個資料夾,也就是一個包

#policy.py
def get():
  print('from policy.py')
#versions.py
def create_resource(conf):
  print('from version.py: ',conf)
#manage.py
def main():
  print('from manage.py')
#models.py
def register_models(engine):
  print('from models.py: ',engine)

  接下來. 我們在test中使用包中的內容. 並且, 我們匯入包的時候可以使用import或者
from xxx import xxx這種形式.
首先, 我們看import

 

import glance.db.models
glance.db.models.register_models('mysql')

沒問題, 很簡單, 我們還可以使用from xxx import xxx 來匯入包內的模組

 

from glance.api.policy import get
get()

  也很簡單, 但是, 要注意. from xxx import xxx這種形式, import後面不可以出現"點" 也
就是說from a.b import c是ok的. 但是 from a import b.c 是錯誤的

 

  好了, 到目前為止, 簡單的包已經可以使用了. 那包里的__init__.py是什麼鬼? 其實. 不論
我們使用哪種方式匯入一個包, 只要是第一次匯入包或者是包的任何其他部分, 都會先執行
__init__.py⽂檔案. 這個檔案可以是空的. 但也可以存放一些初始化的程式碼. (隨意在glance中的
__init__.py都可以進行測試)
那我們之前用的from xxx import *還可以用麼? 可以. 我們要在__init__.py檔案中給出
__all__來確定* 匯入的內容.

 

print("我是glance的__init__.py⽂檔案. ")
x = 10
def hehe():
  print("我是呵呵")
def haha():
  print("我是哈哈")
__all__ = ['x', "hehe"]

test.py

from glance import *
print(x) # OK
hehe() # OK
haha() # 報錯. __all__⾥裡里沒有這個⻤鬼東⻄西

 

 

  接下來, 我們來看一下絕對匯入和相對匯入, 我們的最頂級包glance是寫給別人用的. 然
後再glance包內部也會有彼此之間互相匯入的需求, 這時候就又絕對匯入和相對匯入兩種方
式了.


  1. 絕對匯入: 以glance作為起始
  2. 相對匯入: 用. 或者..作為起始
例如, 我們在glance/api/version.py中使⽤用glance/cmd/manage.py

 

# 在glance/api/version.py

#絕對匯入 from glance.cmd import manage manage.main()
#相對匯入 # 這種情形不可以在versions中啟動程式. # attempted relative import beyond top-level package from ..cmd import manage manage.main()

  測試的時候要注意. python包路徑跟運行指令碼所在的目錄有關係. 說⽩了. 就是你運行的
py檔案所在的目錄. 在python中不允許你運行的程式導包的時候超過當前包的範圍(相對導
入). 如果使用絕對匯入. 沒有這個問題. 換個說法. 如果你在包內使用了相對匯入. 那在使用該
包內資訊的時候. 只能在包外面匯入


接下來. 我們來看一個⼤坑. 比如. 我們想在policy中使用verson中的內容.

# 在policy.py
import versions
ModuleNotFoundError: No module named 'versions'

  如果我們程式的入⼝是policy.py 那此時程式是沒有任何問題的. 但是如果我們在glance
外面import了glance中的policy就會報錯 原因是如果在外⾯訪問policy的時候. sys.path中的
路徑就是外面. 所以根本就不能直接找到versions模組. 所以一定會報錯:

在導包出錯的時候. 一定要先看sys.path 看一下是否真的能獲取到包的資訊.


最後, 我們看一下如何單獨匯入一個包.

# 在test.py中
import glance

  此時匯入的glance什麼都做不了. 因為在glance中的__init__.py中並沒有關於子包的加
載. 此時我們需要在__init__.py中分別取引入子包中的內容.
    1. 使⽤絕對路徑
    2. 使用相對路徑

 


包的注意事項:


  1. 關於包相關的匯入語句也分為import和from xxx import xxx兩種, 但⽆無論使⽤哪種,
無論在什麼位置, 在匯入時都必須遵循一個原則: 凡是在匯入時d帶點的. 點左邊都必須是一
個包. 否則報錯. 可以帶一連串的點. 比如a.b.c


  2. import匯入檔案時. 產⽣名稱空間中的名字來源於檔案, import 包, 產生的名稱空間
中的名字同樣來源於檔案, 即包下的__init__,py, 匯入包本質就是在匯入該檔案


  3. 包A和包B下有同名模組也不會衝突, 如A.a和B.a來⾃自兩個名稱空間