python模組導入-軟體開發目錄規範-01
- 2019 年 10 月 7 日
- 筆記
模組
模組的基本概念
模組: # 一系列功能的結合體
模組的三種來源
""" 模組的三種來源 1.python解釋器內置的模組(os、sys....) 2.第三方的別人寫好的模組文件(requests...) 3.自己定義開發的功能模組(你寫在py文件里的內容,可以被當成模組導入) """
模組的四種表現形式
""" 模組的四種表現形式 1.用python語言編寫的py文件(也就意味著py文件也可以稱之為模組:一個py文件也可以稱之為模組) 2.已被編譯為共享庫或者DLL的C或者C++擴展 3.把一系列模組組織到一起的文件夾(文件夾下有一個__init__.py文件,該文件夾稱之為包(包:一系列py文件的結合體)) 4.使用C編寫並連接到python解釋器的內置模組 """
為什麼用模組
""" 使用模組的原因 1.使用別人寫好的模組(內置的,第三方的),可以極大地提升開發效率 2.可以將自己寫的項目分成多個py文件,多個文件使用相同方法的時候可以寫到一個py文件中,然後以模組的形式導入直接調用(更高層面的程式碼復用) """
tips:現在是高效率社會,誰寫的快,功能強大,bug少,就是NB,不是說都自己純手擼出來才NB
模組引入
使用模組 一定要注意區分哪個是執行文件,哪個是被導入文件
import
使用import關鍵字導入模組,
現有如下兩個文件,執行index.py
print("this is test01") name = 'test01' def hello(): print("hello, i am from test01")
import test01 # 使用import 關鍵字導入模組 print("this is index") # this is test01 # this is index name = 'index' print(name) # index def hello(): print("hello, i am from index!") # hello, i am from index! hello() print(test01.hello()) # 先執行test01中的hello 方法,然後將返回值返回,print列印 # hello, i am from test01 # hello方法中列印的資訊 # None # hello方法的返回值(沒有返回值) print(test01.name) # test01 import test01 # 再次導入模組,並不會再次執行test01 模組中的程式碼
''' 執行index.py 文件,創建一個index 命名空間 執行index.py 的第一行程式碼 import test01 ,先查看index的名稱空間中,沒有指向test01這個名稱空間的內容,所以進入到 test01文件,創建一個test01 名稱空間 執行test01文件: 執行test01第一行程式碼,列印this is test01 執行test01第二行程式碼,在test01 的名稱空間中存儲一個變數name 與字元串test01記憶體地址的綁定關係(即變數名name) 執行test01第三行程式碼(忽略空行),定義一個函數hello,在test01的名稱空間中存儲hello 與其記憶體地址的綁定關係(跳過函數體,函數定義階段不執行)(即函數名) 至此,test01執行完畢,返回index 的import那行語句 在index的名稱空間中存儲一個變數test01 執行test01這個名稱空間的記憶體地址(即變數test01 指向了 test01) --> index的名稱空間中有test01這麼一個東西執行test01模組的名稱空間 執行下一行程式碼,列印this is index 執行下一行程式碼,在index 的名稱空間中存儲一個變數name 與字元串index記憶體地址的綁定關係 執行下一行程式碼,將name 列印出來(在index 這個名稱空間中找,找到了 name = 'index') 執行下一行程式碼,定義一個函數hello,在index名稱空間中存儲hello 與其記憶體地址的綁定關係 執行下一行程式碼(跳過了hello 的函數體程式碼),調用hello 函數(在index這個名稱空間找,找到了hello方法,調用,在控制台列印了hello, i am from index!) 執行下一行程式碼,執行因函數加括弧的執行優先順序比較高,所以先調用函數,因指定了名稱空間是test01 所以就去test01裡面找hello函數,找到,然後列印hello, i am from test01 因test01中的hello函數沒有返回值,所以列印了個None 執行下一行程式碼,列印test01名稱空間中的name變數,字元串test01 執行下一行程式碼,import test01,此時index的名稱空間中已經有test01的名稱空間了,就不再執行test01.py內的程式碼了(python可以避免重複導入) '''

import小結
''' 多次導入同一模組不會再執行模組文件,會沿用第一次導入的成果(******) 使用import導入模組 訪問模組名稱空間中的名字統一句勢:模組名.名字 特點: 1.指名道姓的訪問模組中的名字 永遠不會與執行文件中的名字衝突 2.你如果想訪問模組中名字 必須用模組名.名字的方式 當模組名字比較複雜的情況下 可以給該模組名取別名 import check_.....login(很長的模組名) as check_login(別名) '''
import導入多個模組
import os, sys, time # 導入多個模組可以這樣寫,但不推薦 # 推薦寫法 import os import sys import time
from … import …
''' 利用from...import...句式 缺點: 1.訪問模組中的名字不需要加模組名前綴 2.在訪問模組中的名字可能會與當前執行文件中的名字衝突 from md1 import * # 一次性將md1模組中的名字全部載入過來 不推薦使用 並且你根本不知道到底有哪些名字可以用 默認是找被導入模組里的__all__(這個列表的元素) '''
__all__
一般與 from … import * 組合使用,指定被導入模組中可以被導入的名稱(不寫默認表示所有名稱),限制導入者能夠拿到的名稱個數
# print('from the md1.py') money = 1000 def read1(): print('md',money) def read2(): print('md模組') read1() def change(): global money money = 0 __all__ = ['money','read1','read2']
循環導入
循環導入問題(這裡推薦一篇別人部落格供參考 循環導入問題)
有如下三個文件,你不管執行哪個文件都會報錯(因為它導入了還不存在的名稱(變數)),例如: ImportError: cannot import name 'y'
# m1.py print('from m1.py') from m2 import x y = 'm1'
# m2.py print('from m2.py') from m1 import y x = 'm2'
# run.py import m1
畫一幅圖表示程式碼執行與名稱空間的變化關係,即可知道為什麼報錯了
# 循環導入:不應該出現在程式裡面 # 如果出現循環導入問題, 那麼一定是你的程式設計的不合理 # 循環導入問題在程式設計階段就應該避免
解決方式
方式一: # 將導入語句寫在文件的最下方(在要用到導入模組之前)
# m1.py print('from m1.py') y = 'm1' from m2 import x
# m2.py print('from m2.py') x = 'm2' from m1 import y
方式二: # 在函數內部寫導入語句
# m1.py print('from m1.py') def func1(): from m2 import x print(x) y = 'm1'
# m2.py print('from m2.py') def func1(): from m1 import y print(y) x = 'm2'
雖然可以解決循環導入的問題,但還是盡量不要產生這個問題,設計的時候盡量避免
__name__
文件是被導入還是被執行的判斷方法 意義所在參考文章 python文件的兩種用途
# 當文件被當做執行文件執行的時候__name__列印的結果是__main__ # 當文件被當做模組導入的時候__name__列印的結果是模組名(沒有後綴) if __name__ == '__main__': index1() index2() if __name__ == '__main__': # 快捷寫法 main直接tab鍵即可 index1()
導入模組時的查找順序
此處知識點案例推薦文章 模組的搜索路徑
''' 模組的查找順序 1.先從記憶體中已導入的模組中找 2.內置模組中找 3.從sys.path裡面找(暫時理解成環境變數,依據當前文件來的) 是一個大列表,裡面放了一堆文件路徑,第一個路徑永遠是執行文件所在的文件夾 '''
驗證(前兩個順序跳過,驗證sys.path里查找)
創建下圖所示的目錄層次,並在對應文件中添加如下內容

# run.py import sys print('執行文件查看的結果:', sys.path) from dir1 import m1
# m1.py import sys print('模組m1中查看的結果', sys.path) # import m2 from dir1 import m2 m2.f2()
# m2.py import sys print(sys.path) def f2(): print('from m2')
當你直接執行 run.py 的時候,你會發現文件並不會報錯, 而你直接執行 m1.py 的時候會直接報錯,這是因為文件的搜索路徑是以當前執行文件所在的路徑為準的,你直接執行 m1.py 他就會在同級去找 dir1 目錄,而他找不到,所以報錯,此時如果你把 from dir1 import m2 改成 import m2 再次執行 m1.py 你就會發現他不會報錯了,而你此時去執行 run.py就會報錯,因為 run.py 導入 m1.py 的時候執行到了 import m2 這句程式碼,而在 run.py 的目錄下去找 m2 模組又找不到了 (注意這個搜索起點的轉變)
相對導入與絕對導入
# 一定要搞清楚誰是執行文件,誰是被導入文件(可判斷 __name__ 的值) # 注意:py文件名不應該與模組名(內置,第三方)衝突 --> 試試文件名衝突,取別名 # sys.path 是以當前被執行文件(右鍵run)為準的
絕對導入
''' 絕對導入必須依據執行文件所在的文件夾路徑為準 1.絕對導入無論在執行文件中還是被導入文件都適用 '''
相對導入
''' 相對導入 .代表當前路徑 ..代表上一級路徑 ...代表上上一級路徑 '''
注意
''' 相對導入不能在執行文件中導入(即,用了相對導入,該文件就不能是執行文件了,只能是模組。。。) 相對導入只能在被導入的模組中使用,使用相對導入,就不需要考慮執行文件到底是誰,只需要知道模組與模組之間的路徑關係 '''
相對導入的相對是針對執行文件而言的,不是以被導入的文件為基準
軟體開發目錄規範
為了提高程式的可讀性與可維護性,我們應該為軟體設計良好的目錄結構,這與規範的編碼風格同等重要,簡而言之就是把軟體程式碼分文件目錄。
軟體基本目錄結構

''' 項目名 bin ..........執行文件 start.py --------項目啟動文件 conf ..........裡面放的是一些變數與值的對應關係,不常變動的值(常量) settings.py --------項目配置文件 core ..........核心邏輯程式碼 src.py --------項目核心邏輯文件 db ..........資料庫相關資訊 modles.py --------項目存儲資料庫 lib ..........一些公共的功能 common.py --------項目所用到的一些公共的功能 log ..........日誌 記錄用戶行為 view.log --------項目的日誌文件 readme.md ..........這款軟體的介紹........... 如果把啟動文件放在項目根目錄,只需要BASE_DIR 改一下就行了 '''
各文件基本內容
''' 歩鄹: 1.拼接項目的根路徑。放到項目的環境變數里 2.導入項目核心入口文件(core/src.py),加判斷,在此文件作為執行文件被載入的時候運行項目核心入口文件(core/src.py)(被導入時不執行) ''' import os import sys # .................歩鄹一 # 這裡是在拼接文件目錄,因為不同作業系統表示文件路徑的間隔符不一致,所以需要用到模組來拼接路徑 BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # # 如果是把軟體的啟動文件(start.py)放到了項目的根目錄,則使用下面的路徑 # BASE_DIR = os.path.dirname(__file__) # 將拼接好的路徑放到 sys.path 中,方便後續import 模組的時候可以直接從項目根目錄出發(查找順序,找不到,然後找到了這裡) sys.path.append(BASE_DIR) # .................歩鄹二 # 這裡請忽略pycharm的報錯,pycharm還不能做到這麼智慧地去識別這個模組存不存在,按照簡單的規則去找找不到 from core import src if __name__ == '__main__': src.run()
''' 這裡是程式的入口 在這裡寫一些項目的核心程式碼 # 可以先用空函數來羅列功能,把功能框架搭好,然後再慢慢去完善程式碼 ''' def register(): pass def login(): pass def shopping(): pass # 這個是start.py 文件導入的開始文件,必須和那邊名字一樣 def run(): print("run了") pass
import os import sys BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # 如果start.py 是在項目根目錄,則使用下方的 BASE_DIR # BASE_DIR = os.path.dirname(__file__) sys.path.append(BASE_DIR) # 其他配置資訊
將上述三個文件寫完,就可以直接在 start.py 右鍵運行將程式跑起來了,現階段簡單的部分其他文件夾用不太到
tips:pycharm會自動把項目根目錄加到 sys.path 裡面去,但我們還是要在 bin/start.py 里配置 BASE_DIR,因為軟體寫來是給別人用的(換了台電腦,位置什麼變了,不用pycharm什麼的,你也得確保它能跑起來)
通用方法編寫(為方法加上過濾條件,需要登錄才能購物),讓其他方法也能用上這個登錄驗證,只需要改動一下 core/src.py 與 lib/common.py 即可,其他要的地方再導入
''' 給shopping方法添加了登錄驗證裝飾器,需要登陸成功才能購物 ''' # 這裡需要把通用模組里寫好的登錄驗證倒過來,然後裝飾器調用 from lib import common # 請忽略pycharm報錯,寫好運行一下你就知道沒問題了 def register(): print("註冊") pass def login(): while True: username = input("Please input your username>>>:").strip() pwd = input("Please input your password>>>:").strip() if username == 'jason' and pwd == '123': print("登錄成功") login_status['is_login'] = True break print("您的帳號或密碼有誤,請重新登陸!") # 這個是common 模組寫的登錄驗證 @common.login_auth def shopping(): print('購物') login_status = { 'is_login': None } func_list = { '0': [register, '註冊'], '1': [login, '登錄'], '2': [shopping, '購物'], } # 這個是start.py 文件導入的開始文件,必須和那邊名字一樣 def run(): print("----功能清單如下----") for i in func_list: print(f"{i}. {func_list.get(i)[1]}") choice = input("請輸入功能編號>>>:").strip() if choice in func_list: func_list.get(choice)[0]()
''' 這裡編寫登錄驗證裝飾器 由於需要用到src.py 中的 login_status、login方法,所以要把 src 導入進來 ''' from core import src # 登錄驗證裝飾器 def login_auth(func): def inner(*args, **kwargs): # 檢查用戶登錄 if not src.login_status.get('is_login'): print("請先登錄!") src.login() res = func(*args, **kwargs) return res return inner