模塊
1、什么是模塊?
一個模塊就是一個Python文件,文件名就是模塊名字加上.py后綴。因此模塊名稱也必須符合變量名的命名規范。
1 使用python編寫的代碼(.py文件)
2 已被編譯為共享庫或DLL的C或C++擴展
3 包好一組模塊的包
4 使用C編寫並鏈接到python解釋器的內置模塊
2、為什么要使用模塊?
如果你退出python解釋器然后重新進入,那么你之前定義的函數或者變量都將丟失,因此我們通常將程序寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。
隨着程序的發展,功能越來越多,為了方便管理,我們通常將程序分成一個個的文件,這樣做程序的結構更清晰,方便管理。這時我們不僅僅可以把這些文件當做腳本去執行,還可以把他們當做模塊來導入到其他的模塊中,實現了功能的重復利用,
3、如何使用模塊?
- 方式一:import
- 方式二:from ... import ...
import
首先,自定義一個模塊my_module.py,文件名my_module.py,模塊名my_module

name = "我是自定義模塊的內容..." def func(): print("my_module: ", name) print("模塊中打印的內容...")
在import一個模塊的過程中,發生了哪些事情?
# 用import導入my_module模塊 import my_module >>> 模塊中打印的內容... # 怎么回事,竟然執行了my_module模塊中的print語句 import my_module import my_module import my_module import my_module import my_module >>> 模塊中打印的內容... # 只打印一次
從上面的結果可以看出,import一個模塊的時候相當於執行了這個模塊,而且一個模塊是不會重復被導入的,只會導入一次(python解釋器第一次就把模塊名加載到內存中,之后的import都只是在對應的內存空間中尋找。)成功導入一個模塊后,被導入模塊與文本之間的命名空間的問題,就成為接下來要搞清楚的概念了。
被導入模塊與本文件之間命名空間的關系?
假設當前文件也有一個變量為: name = 'local file', 也有一個同名的func方法。
# 本地文件 name = "local file" def func(): print(name) # 本地文件有跟被導入模塊同名的變量和函數,究竟用到的是哪個呢? import my_module print(my_module.name) # 根據結果可以看出,引用的是模塊里面的name my_module.func() # 執行的是模塊里面的func()函數 >>> 模塊中打印的內容... 我是自定義模塊的內容... my_module: 我是自定義模塊的內容... print(name) # 使用的是本地的name變量 func() # 使用的是本地的func函數 >>> local file local file
在import模塊的時候發生了下面的幾步:
1、先尋找模塊
2、如果找到了,就在內存中開辟一塊空間,從上至下執行這個模塊
3、把這個模塊中用到的對象都收錄到新開辟的內存空間中
4、給這個內存空間創建一個變量指向這個空間,用來引用其內容。
總之,模塊與文件之間的內存空間始終是隔離的
給導入的模塊取別名,用as關鍵字
如果導入的模塊名太長不好記,那么可以通過“import 模塊名 as 別名”的方式給模塊名取一個別名,但此時原來的模塊就不再生效了(相當於創建了新的變量名指向模塊內存空間,斷掉原模塊名的引用)。
# 給my_module模塊取別名 import my_module as sm print(sm.name) >>> 我是自定義模塊的內容... print(my_module.name) # 取了別名后,原來的模塊名就不生效了 >>> NameError: name 'my_module' is not defined
給模塊去別名,還可以使代碼更加靈活,減少冗余,常用在根據用戶輸入的不同,調用不同的模塊。
# 按照先前的做法,寫一個函數,根據用戶傳入的序列化模塊,使用對應的方法 def dump(method): if method == 'json': import json with open('dump.txt', 'wb') as f: json.dump('xxx', f) elif method == 'pickle': import pickle with open('dump.txt', 'wb') as f: pickle.dump('xxx', f) # 上面的代碼冗余度很高,如果簡化代碼?通過模塊取別名的方式,可以減少冗余 def dump(method): if method == 'json': import json as m elif method == 'pickle': import pickle as m with open('dump.txt', 'wb') as f: m.dump('dump.txt', f)
如何同時導入多個模塊?
方式一:每行導入一個模塊
import os import sys import time
方式二:一行導入多個模塊,模塊之間通過逗號“,”來分隔
import os, sys, my_module
但是,根據PEP8規范規定使用第一種方式,並且三種模塊有先后順序(內置>第三方>自定義)
# 根據PEP8規范 import os import django import my_module
模塊搜索路徑
通過sys內置模塊,我們知道sys.path存儲了所有模塊的路徑,但是正常的sys.path的路徑中除了內置模塊,第三方模塊所在的路徑之外,只有一個路徑是永遠正確的,就是當前執行的文件所在目錄。一個模塊是否能夠被導入,就取決於這個模塊所在的目錄是否在sys.path中。
python解釋器在啟動時會自動加載一些模塊,可以使用sys.modules查看
在第一次導入某個模塊時(比如my_module),會先檢查該模塊是否已經被加載到內存中(當前執行文件的名稱空間對應的內存),如果有則直接引用
如果沒有,解釋器則會查找同名的內建模塊,如果還沒有找到就從sys.path給出的目錄列表中依次尋找my_module.py文件。
所以總結模塊的查找順序是:內存中已經加載的模塊->內置模塊->sys.path路徑中包含的模塊
需要特別注意的是:我們自定義的模塊名不應該與系統內置模塊重名。
模塊和腳本
運行一個py文件有兩種方式,但是這兩種執行方式之間有一個明顯的差別,就是__name__。
1、已腳本的方式執行:cmd中“python xxx.py” 或者pycharm等IDE中執行
__name__ = '__main__'
2、導入模塊時執行:import模塊,會執行該模塊。
__name__ = 模塊名
然而,當你有一個py文件既可以作為腳本執行,又可以作為模塊提供給其他模塊引用時,這時作為模塊需要導入時而不顯示多余的打印邏輯/函數調用,所以這些邏輯可以放在“if __name__ = '__main__': xxx” 代碼塊中。
這樣py文件作為腳本執行的時候就能夠打印出來,以模塊被導入時,便不會打印出來。
from ... import ...
from...import是另一種導入模塊的形式,如果你不想每次調用模塊的對象都加上模塊名,就可以使用這種方式。
在from ... import ... 的過程中發生了什么事兒?
from my_module import name, func print(name) # 此時引用模塊中的對象時,就不要再加上模塊名了。 func()
1、尋找模塊
2、如果找到模塊,在內存中開辟一塊內存空間,從上至下執行模塊
3、把模塊中的對應關系全部都保存到新開辟的內存空間中
4、建立一個變量xxx引用改模塊空間中對應的xxx, 如果沒有import進來的時候,就使用不了。
from ... import ... 方式取別名
與import方式如出一轍,通過"from 模塊名 import 對象名 as 別名"。
from my_module import name as n, func as f
from ... import *
import * 相當於把這個模塊中的所有名字都引入到當前文件中,但是如果你自己的py文件如果有重名的變量,那么就會產生不好的影響,因此使用from...import *時需要謹慎,不建議使用。
* 與 __all__
__all__是與*配合使用的,在被導入模塊中增加一行__all__=['xxx','yyy'],就規定了使用import *是只能導入在__all__中規定的屬性。
# 在my_module模塊中定義__all__ __all__ = ['name'] name = 'My module...' def func(): print("my_module: ", name) # 在其他文件中通過import *導入所有屬性 from my_module import * print(name) >>> My module... func() >>> NameError: name 'func' is not defined
拓展知識點:
(1)pyc文件與pyi文件 *
pyi文件:跟.py一樣,僅僅作為一個python文件的后綴名。
pyc文件: python解釋器為了提高加載模塊的速度,會在__pycache__目錄中生成模塊編譯好的字節碼文件,並且對比修改時間,只有模塊改變了,才會再次編譯。pyc文件僅僅用於節省了啟動時間,但是並不能提高程序的執行效率。
(2)模塊的導入和修改 *
1.導入模塊后,模塊就已經被加載到內存中,此后計算對模塊進行改動,讀取的內容還是內存中原來的結果。
2.如果想讓改動生效,可以通過“from importlib import reload”, 需要'reload 模塊名'重新加載模塊,改動才生效。
(3)模塊的循環使用 ****
謹記模塊的導入必須是單鏈的,不能有循環引用,如果存在循環,那么就是程序設計存在問題。
(4)dir(模塊名) ***
可以獲得該模塊中所有的名字,而且是字符串類型的,就可以通過反射去執行它。
包
包是一種通過‘.模塊名’來組織python模塊名稱空間的方式。
(1)無論是import形式還是from ... import 形式,凡是在導入語句中(而不是在使用時)遇到帶點的,都要第一時間提高警覺:這是關於包才有的導入語法
(2)包是目錄級的(文件夾級),文件夾是用來組成py文件(包的本質就是一個包含__init__.py文件的目錄)
(3)import導入文件時,產生名稱空間中的名字來源與文件,import包,產生的名稱空間的名字同樣來源與文件,即包下的__init__.py,導入包本質就是在導入文件
注意:
1、在python3中,即使包下沒有__init__.py文件,import包仍然不會報錯,而在python2中,包下一定要有該文件,否則import包會報錯
2、創建包的目的不是為了運行,而是被導入使用,記住,包只有模塊的一種形式而已,包即模塊
包A和包B下有同名模塊也不會沖突,如A.a與B.a來自兩個命令空間
示例環境如下:

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/models.py','w'))
map(lambda f:f.close() ,l)

glance/ #Top-level package
├── __init__.py #Initialize the glance package
├── api #Subpackage for api
│ ├── __init__.py
│ ├── policy.py
│ └── versions.py
├── cmd #Subpackage for cmd
│ ├── __init__.py
│ └── manage.py
└── db #Subpackage for db
│ ├── __init__.py
│ └── models.py

#文件內容
#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)
從包中導入模塊
(1)從包中導入模塊有兩種方式,但是無論哪種,無論在什么位置,都必須遵循一個原則:(凡是在導入時帶點的,點的左邊都必須是一個包),否則非法。
(2)對於導入后,在使用就沒有這種限制,點的左邊可以是包,模塊,函數,類(它們都可以用點的方式調用自己的屬性)
(3)對比import item 和from item import name的應用場景:如果我們想直接使用name那么必須使用后者。
方式一:import
例如: 包名1.包名2.包名3.模塊名
# 在與包glance同級別的文件中測試 import glance.db.models glance.db.models.register_models('mysql') """執行結果:from models.py mysql"""
方式二:from ... import ...
例如:from 包名1.包名2 import 模塊名
from 包名1.包名2.模塊名 import 變量名/函數名/變量名
注意:需要注意的是from后import導入的模塊,必須是明確的一個不能帶點,否則會有語法錯誤,如:from a import b.c是錯誤語法
# 在與包glance同級別的文件中測試 from glance.db import models models.register_models('mysql') """執行結果:from models.py mysql""" from glance.cmd import manage manage.main() """執行結果:from manage.py"""
直接導入包
如果是直接導入一個包,那么相當於執行了這個包中的__init__文件
並不會幫你把這個包下面的其他包以及py文件自動的導入到內存
如果你希望直接導入包之后,所有的這個包下面的其他包以及py文件都能直接通過包來調用,那么需要你自己處理__init__文件。
__init__.py文件
不管是哪種方式,只要是第一次導入包或者是包的任何其他部分,都會依次執行包下的__init__.py文件;這個文件可以為空,但是也可以存放一些初始化包的代碼。
絕對導入和相對導入
我們的最頂級包glance是寫給別人用的,然后在glance包內部也會有彼此之間互相導入的需求,這時候就有絕對導入和相對導入兩種方式:
絕對導入:以glance作為起始
相對導入:用. 或者.. 的方式作為起始(只能在一個包中使用,不能用於不同目錄內)
絕對導入和絕對導入示例:
絕對導入: 既然導入包就是執行包下的__init__.py文件,那么嘗試在啊glance的__init__.py文件中"import api",執行一下,貌似沒有報錯,在嘗試下在包外導入,情況如何? 在包外創建一個test.py文件,在里面操作如下: import glance glance.api ModuleNotFoundError: No module named 'api' 原因:為什么還會報錯?因為一個模塊能不能被導入就看在sys.path中有沒有路徑,在哪里執行文件,sys.path永遠記錄該文件的目錄。 (1)在glance的__init__.py文件中,sys.path的路徑是: 'E:\\Python練習\\包\\glance' 所以能夠找到同級的api (2)但是在test文件中導入,此時sys.path的路徑是: 'E:\\李彥傑\\Python練習\\包' 所以找不到不同層級的api,所以就會報No module name 'api' 解決辦法一: 使用絕對路徑(絕對路徑為當前執行文件的目錄) (1)在glance包中的__init__.py中通過絕對路徑導入: "from glance import api" (2)這樣在test文件中執行,就能找到同層級的glance,再去里面找api (3)同理,如果想使用api包中的模塊,也要在api包中的__init__.py文件中導入"from glance.api import policy, veersions", (4)現在在test文件中調用glance下的api下的policy模塊就不會報錯: import glance glance.api.policy.get() glance.api.versions.create_resource('測試') 執行結果: from policy.py from versions.py 測試 絕對導入的缺點: 如果以后包的路徑發生了轉移,包內的所有__init__.py文件中的絕對路徑都需要改變 解決辦法二: 使用相對導入 . 表示當前目錄 .. 表示上一級目錄 (1)在glance包中的__init__.py中通過相對路徑的形式導入: “from . import api” (2)同理在api包中的__init__.py中通過相對路徑的形式導入: “from . import policy,version” (3)同樣在test文件中調用glance下的api下的policy模塊就不會報錯: import glance glance.api.policy.get() glance.api.versions.create_resource('測試') 執行結果: from policy.py from versions.py 測試 相對導入的優點: 包發生路徑轉移,其中的相對路徑都沒有改變,所以不用逐個逐個修改。 相對導入的缺點: 但凡帶着相對導入的文件,只能當做模塊導入,不能作為一個腳本單獨執行!!!
擴展知識:
同級目錄下的包導入
需求:現在需要在bin下面的start文件中導入core目錄下的main模塊;怎么破?
project ├── bin #Subpackage for bin ├── __init__.py └── start.py ├── core #Subpackage for core ├── __init__.py └── main.py
# main.py文件中的內容: def func(): print("In main")
(1)、在start中直接導入,因為路徑不對,所以直接報錯:
import main # 執行,報錯ModuleNotFoundError: No module named 'main'
(2)、由上面報錯我們知道肯定路徑不對,那么我們想到直接將core路徑加進去不就好了嗎?是的,這樣是可行的
import sys path = 'E:\練習\包\core' # 復制得到core的絕對路徑 sys.path.append(path) # 將core路徑添加 import main # 再次導入便不會報錯 main.func() # 執行結果:In main
(3)、上面的方法看似可行,但是還是有一個問題,如果我將project打包發給別人,或者我換個環境運行呢? 那么又得更改對應的path。不怎么合理,那么我們看下面的方法:
import sys print(__file__) ret = __file__.split('/') base_path = '/'.join(ret[:-2]) sys.path.append(base_path) from core import main main.func() # In main
1、__file__ 可以得到當前文件的絕對路徑,E:/練習/project/bin/start.py
2、__file__.split('/') 將當前文件的絕對路徑進行處理,按照'/'分隔得到:['E:', '練習', 'project', 'bin', 'start.py']
3、'/'.join(ret[:-2]) 因為我們只需要拿到project項目的動態路徑,所以進行切割,在jojn得到: E:/練習/project
4、sys.path.append(base_path) 再將得到的路徑添加到sys.path中
5、from core import main 因為我們拿到的是project目錄,所以導入是從當前路徑的core包導入main模塊
6、main.func() 最后再是模塊名.方法。