萬惡之源 - Python包的應用
包的簡介
Packages are a way of structuring Python’s module namespace by using “dotted module names” 包是一種通過使用‘.模組名’來組織python模組名稱空間的方式。 #具體的:包就是一個包含有__init__.py檔案的資料夾,所以其實我們建立包的目的就是為了用資料夾將檔案/模組組織起來 #需要強調的是: 1. 在python3中,即使包下沒有__init__.py檔案,import 包仍然不會報錯,而在python2中,包下一定要有該檔案,否則import 包報錯 2. 建立包的目的不是為了執行,而是被匯入使用,記住,包只是模組的一種形式而已,包的本質就是一種模組
為什麼要使用包呢? 包的本質就是一個資料夾,那麼資料夾唯一的功能就是將檔案組織起來 隨著功能越寫越多,我們無法將所以功能都放到一個檔案中,於是我們使用模組去組織功能,而隨著模組越來越多,我們就需要用資料夾將模組檔案組織起來,以此來提高程式的結構性和可維護性
注意事項
#1.關於包相關的匯入語句也分為import和from ... import ...兩種,但是無論哪種,無論在什麼位置,在匯入時都必須遵循一個原則:凡是在匯入時帶點的,點的左邊都必須是一個包,否則非法。可以帶有一連串的點,如item.subitem.subsubitem,但都必須遵循這個原則。但對於匯入後,在使用時就沒有這種限制了,點的左邊可以是包,模組,函式,類(它們都可以用點的方式呼叫自己的屬性)。 #2、import匯入檔案時,產生名稱空間中的名字來源於檔案,import 包,產生的名稱空間的名字同樣來源於檔案,即包下的__init__.py,匯入包本質就是在匯入該檔案 #3、包A和包B下有同名模組也不會衝突,如A.a與B.a來自倆個名稱空間
包的使用
示例檔案
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) 包所包含的檔案內容
執行檔案與示範檔案在同級目錄下
包的使用之import
1 import glance.db.models 2 glance.db.models.register_models('mysql')
單獨匯入包名稱時不會匯入包中所有包含的所有子模組,如
#在與glance同級的test.py中 import glance glance.cmd.manage.main() ''' 執行結果: AttributeError: module 'glance' has no attribute 'cmd' '''
解決方法:
1 #glance/__init__.py 2 from . import cmd 3 4 #glance/cmd/__init__.py 5 from . import manage
1 #在於glance同級的test.py中 2 import glance 3 glance.cmd.manage.main()
包的使用之from ... import ...
需要注意的是from後import匯入的模組,必須是明確的一個不能帶點,否則會有語法錯誤,如:from a import b.c是錯誤語法
from glance.api import *
在講模組時,我們已經討論過了從一個模組內匯入所有,此處我們研究從一個包匯入所有。
此處是想從包api中匯入所有,實際上該語句只會匯入包api下_init**.py檔案中定義的名字,我們可以在這個檔案中定義all**:
#在__init__.py中定義 x=10 def func(): print('from api.__init.py') __all__=['x','func','policy']
此時我們在於glance同級的檔案中執行from glance.api import *就匯入all中的內容(versions仍然不能匯入)。
#在__init__.py中定義 x=10 def func(): print('from api.__init.py') __all__=['x','func','policy']
此時我們在於glance同級的檔案中執行from glance.api import *就匯入all中的內容(versions仍然不能匯入)。
練習:
#執行檔案中的使用效果如下,請處理好包的匯入 from glance import * get() create_resource('a.conf') main() register_models('mysql') #在glance.__init__.py中 from .api.policy import get from .api.versions import create_resource from .cmd.manage import main from .db.models import register_models __all__=['get','create_resource','main','register_models']
絕對匯入和相對匯入
我們的最頂級包glance是寫給別人用的,然後在glance包內部也會有彼此之間互相匯入的需求,這時候就有絕對匯入和相對匯入兩種方式:
絕對匯入:以glance作為起始
相對匯入:用.或者..的方式最為起始(只能在一個包中使用,不能用於不同目錄內)
例如:我們在glance/api/version.py中想要匯入glance/cmd/manage.py
1 在glance/api/version.py 2 3 #絕對匯入 4 from glance.cmd import manage 5 manage.main() 6 7 #相對匯入 8 from ..cmd import manage 9 manage.main()
測試結果:注意一定要在於glance同級的檔案中測試
1 from glance.api import versions
包以及包所包含的模組都是用來被匯入的,而不是被直接執行的。而環境變數都是以執行檔案為準的
比如我們想在glance/api/versions.py中匯入glance/api/policy.py,有的同學一抽這倆模組是在同一個目錄下,十分開心的就去做了,它直接這麼做
1 #在version.py中 2 3 import policy 4 policy.get()
沒錯,我們單獨執行version.py是一點問題沒有的,執行version.py的路徑搜尋就是從當前路徑開始的,於是在匯入policy時能在當前目錄下找到
但是你想啊,你子包中的模組version.py極有可能是被一個glance包同一級別的其他檔案匯入,比如我們在於glance同級下的一個test.py檔案中匯入version.py,如下
1 from glance.api import versions 2 3 ''' 4 執行結果: 5 ImportError: No module named 'policy' 6 ''' 7 8 ''' 9 分析: 10 此時我們匯入versions在versions.py中執行 11 import policy需要找從sys.path也就是從當前目錄找policy.py, 12 這必然是找不到的 13 '''
絕對匯入與相對匯入總結
絕對匯入與相對匯入 # 絕對匯入: 以執行檔案的sys.path為起始點開始匯入,稱之為絕對匯入 # 優點: 執行檔案與被匯入的模組中都可以使用 # 缺點: 所有匯入都是以sys.path為起始點,匯入麻煩 # 相對匯入: 參照當前所在檔案的資料夾為起始開始查詢,稱之為相對匯入 # 符號: .代表當前所在檔案的檔案加,..代表上一級資料夾,...代表上一級的上一級資料夾 # 優點: 匯入更加簡單 # 缺點: 只能在匯入包中的模組時才能使用 #注意: 1. 相對匯入只能用於包內部模組之間的相互匯入,匯入者與被匯入者都必須存在於一個包內 2. attempted relative import beyond top-level package # 試圖在頂級包之外使用相對匯入是錯誤的,言外之意,必須在頂級包內使用相對匯入,每增加一個.代表跳到上一級資料夾,而上一級不應該超出頂級包
random模組
>>> import random #隨機小數 >>> random.random() # 大於0且小於1之間的小數 0.7664338663654585 >>> random.uniform(1,3) #大於1小於3的小數 1.6270147180533838 #恆富:發紅包 #隨機整數 >>> random.randint(1,5) # 大於等於1且小於等於5之間的整數 >>> random.randrange(1,10,2) # 大於等於1且小於10之間的奇數 #隨機選擇一個返回 >>> random.choice([1,'23',[4,5]]) # #1或者23或者[4,5] #隨機選擇多個返回,返回的個數為函式的第二個引數 >>> random.sample([1,'23',[4,5]],2) # #列表元素任意2個組合 [[4, 5], '23'] #打亂列表順序 >>> item=[1,3,5,7,9] >>> random.shuffle(item) # 打亂次序 >>> item [5, 1, 3, 7, 9] >>> random.shuffle(item) >>> item [5, 9, 7, 1, 3]
練習:生成隨機驗證碼
import random def v_code(): code = '' for i in range(5): num=random.randint(0,9) alf=chr(random.randint(65,90)) add=random.choice([num,alf]) code="".join([code,str(add)]) return code print(v_code())
列印進度條
#=========知識儲備========== #進度條的效果 [# ] [## ] [### ] [#### ] #指定寬度 print('[%-15s]' %'#') print('[%-15s]' %'##') print('[%-15s]' %'###') print('[%-15s]' %'####') #列印% print('%s%%' %(100)) #第二個%號代表取消第一個%的特殊意義 #可傳參來控制寬度 print('[%%-%ds]' %50) #[%-50s] print(('[%%-%ds]' %50) %'#') print(('[%%-%ds]' %50) %'##') print(('[%%-%ds]' %50) %'###') #=========實現列印進度條函式========== import sys import time def progress(percent,width=50): if percent >= 1: percent=1 show_str = ('%%-%ds' % width) % (int(width*percent)*'|') print('\r%s %d%%' %(show_str, int(100*percent)), end='') #=========應用========== data_size=1025 recv_size=0 while recv_size < data_size: time.sleep(0.1) #模擬資料的傳輸延遲 recv_size+=1024 #每次收1024 percent=recv_size/data_size #接收的比例 progress(percent,width=70) #進度條的寬度70
shutil
shutil模組 高階的檔案、資料夾、壓縮包 處理模組
shutil.copyfileobj(fsrc, fdst[, length]) 將檔案內容拷貝到另一個檔案中
import shutil shutil.copyfileobj(open('old.xml','r'), open('new.xml', 'w'))
shutil.copyfile(src, dst) 拷貝檔案
shutil.copyfile('f1.log', 'f2.log') #目標檔案無需存在
shutil.copymode(src, dst) 僅拷貝許可權。內容、組、使用者均不變
shutil.copymode('f1.log', 'f2.log') #目標檔案必須存在
shutil.copystat(src, dst) 僅拷貝狀態的資訊,包括:mode bits, atime, mtime, flags
shutil.copystat('f1.log', 'f2.log') #目標檔案必須存在
shutil.copy(src, dst) 拷貝檔案和許可權
import shutil shutil.copy('f1.log', 'f2.log') shutil.copy2(src, dst)
拷貝檔案和狀態資訊
import shutil shutil.copy2('f1.log', 'f2.log') shutil.ignore_patterns(*patterns) shutil.copytree(src, dst, symlinks=False, ignore=None)
遞迴的去拷貝資料夾
import shutil shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) #目標目錄不能存在,注意對folder2目錄父級目錄要有可寫許可權,ignore的意思是排除
拷貝軟連線
import shutil shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) ''' 通常的拷貝都把軟連線拷貝成硬連結,即對待軟連線來說,建立新的檔案 '''
shutil.rmtree(path[, ignore_errors[, onerror]]) 遞迴的去刪除檔案
import shutil shutil.rmtree('folder1') shutil.move(src, dst)
遞迴的去移動檔案,它類似mv命令,其實就是重新命名。
import shutil shutil.move('folder1', 'folder3') shutil.make_archive(base_name, format,...)
建立壓縮包並返回檔案路徑,例如:zip、tar
base_name: 壓縮包的檔名,也可以是壓縮包的路徑。只是檔名時,則儲存至當前目錄,否則儲存至指定路徑, 如 data_bak =>儲存至當前路徑 如:/tmp/data_bak =>儲存至/tmp/ format: 壓縮包種類,“zip”, “tar”, “bztar”,“gztar” root_dir: 要壓縮的資料夾路徑(預設當前目錄) owner: 使用者,預設當前使用者 group: 組,預設當前組 logger: 用於記錄日誌,通常是logging.Logger物件 #將 /data 下的檔案打包放置當前程式目錄 import shutil ret = shutil.make_archive("data_bak", 'gztar', root_dir='/data') #將 /data下的檔案打包放置 /tmp/目錄 import shutil ret = shutil.make_archive("/tmp/data_bak", 'gztar', root_dir='/data')
shutil 對壓縮包的處理是呼叫 ZipFile 和 TarFile 兩個模組來進行的,詳細: zipfile壓縮解壓縮
import zipfile # 壓縮 z = zipfile.ZipFile('laxi.zip', 'w') z.write('a.log') z.write('data.data') z.close() # 解壓 z = zipfile.ZipFile('laxi.zip', 'r') z.extractall(path='.') z.close()
tarfile壓縮解壓縮
import tarfile # 壓縮 >>> t=tarfile.open('/tmp/egon.tar','w') >>> t.add('/test1/a.py',arcname='a.bak') >>> t.add('/test1/b.py',arcname='b.bak') >>> t.close() # 解壓 >>> t=tarfile.open('/tmp/egon.tar','r') >>> t.extractall('/egon') >>> t.close()