一、什麼是模塊?python
一個模塊就是一個Python文件,文件名就是模塊名字加上.py後綴。所以模塊名稱也必須符合變量名的命名規範。mysql
1 使用python編寫的代碼(.py文件)sql
2 已被編譯爲共享庫或DLL的C或C++擴展django
3 包好一組模塊的包json
4 使用C編寫並連接到python解釋器的內置模塊api
二、爲何要使用模塊?app
若是你退出python解釋器而後從新進入,那麼你以前定義的函數或者變量都將丟失,所以咱們一般將程序寫到文件中以便永久保存下來,須要時就經過python test.py方式去執行,此時test.py被稱爲腳本script。ide
隨着程序的發展,功能愈來愈多,爲了方便管理,咱們一般將程序分紅一個個的文件,這樣作程序的結構更清晰,方便管理。這時咱們不只僅能夠把這些文件當作腳本去執行,還能夠把他們當作模塊來導入到其餘的模塊中,實現了功能的重複利用,函數
三、如何使用模塊?測試
首先,自定義一個模塊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模塊的時候發生了下面的幾步:
一、先尋找模塊
二、若是找到了,就在內存中開闢一塊空間,從上至下執行這個模塊
三、把這個模塊中用到的對象都收錄到新開闢的內存空間中
四、給這個內存空間建立一個變量指向這個空間,用來引用其內容。
總之,模塊與文件之間的內存空間始終是隔離的
給導入的模塊取別名,用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__。
一、已腳本的方式執行:cmd中「python xxx.py」 或者pycharm等IDE中執行
__name__ = '__main__'
二、導入模塊時執行:import模塊,會執行該模塊。
__name__ = 模塊名
然而,當你有一個py文件既能夠做爲腳本執行,又能夠做爲模塊提供給其餘模塊引用時,這時做爲模塊須要導入時而不顯示多餘的打印邏輯/函數調用,因此這些邏輯能夠放在「if __name__ = '__main__': xxx」 代碼塊中。
這樣py文件做爲腳本執行的時候就可以打印出來,以模塊被導入時,便不會打印出來。
from...import是另外一種導入模塊的形式,若是你不想每次調用模塊的對象都加上模塊名,就可使用這種方式。
在from ... import ... 的過程當中發生了什麼事兒?
from my_module import name, func print(name) # 此時引用模塊中的對象時,就不要再加上模塊名了。 func()
一、尋找模塊
二、若是找到模塊,在內存中開闢一塊內存空間,從上至下執行模塊
三、把模塊中的對應關係所有都保存到新開闢的內存空間中
四、創建一個變量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,導入包本質就是在導入文件
注意:
一、在python3中,即便包下沒有__init__.py文件,import包仍然不會報錯,而在python2中,包下必定要有該文件,不然import包會報錯
二、建立包的目的不是爲了運行,而是被導入使用,記住,包只有模塊的一種形式而已,包即模塊
包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
一、__file__ 能夠獲得當前文件的絕對路徑,E:/練習/project/bin/start.py
二、__file__.split('/') 將當前文件的絕對路徑進行處理,按照'/'分隔獲得:['E:', '練習', 'project', 'bin', 'start.py']
三、'/'.join(ret[:-2]) 由於咱們只須要拿到project項目的動態路徑,因此進行切割,在jojn獲得: E:/練習/project
四、sys.path.append(base_path) 再將獲得的路徑添加到sys.path中
五、from core import main 由於咱們拿到的是project目錄,因此導入是從當前路徑的core包導入main模塊
六、main.func() 最後再是模塊名.方法。