Python自動化開發學習5
模塊
在模塊中,我們可以定義變量、函數,還有類(這個還沒學到)。總之應該就是所有的代碼。先建一個文件,取名為module,py,內容如下:
# 一下是module.py的內容 string = "This is module,py" def say_hi(): print("Hi") def test(): return "test in module.py"
在上面的模塊中我們定義了1個變量和2個函數,現在我們要在另外一個文件中導入這個模塊的一部分或者全部
導入模塊
import 導入模塊
import module print(module.string) module.say_hi()
上面就可以導入並且調用模塊中的方法。
如果需要導入多個模塊,可以一次import全部導入,只需要用逗號隔開
import module_name1,module_name2,module_name3
from import 導入模塊
from module import string,test print(string) print(test())
向上面這樣,可以指定導入一部分,而不用全部導入。但是調用的方法也不同了,這裏需要註意。
from module import *
使用*可以一次全部導入,但是這個用法不推薦使用。或者在模塊中定義一個__all__的列表(這部分上課沒講),那麽*只能導入all列表中存在的對象。
之所以不推薦使用,主要還是調用的方法導致的問題。如果在調用的文件中也有一個同名的變量,就會替換掉import來的變量,比如下面這樣:
from module import string,test string = "This is main" def test(): return "test in main" print(string) print(test())
所以使用*這種不可控的導入方式,不推薦。但是還是沒解決調用文件和導入文件裏變量同名的問題,如果2邊都不能改的話。這裏還需要用到下面的as
form import as 導入模塊
from module import test as module_test def test(): return "test in main" print(test()) print(module_test())
上面使用as起了別名後,就沒有同名的問題了。不過這裏沒法同時導入多個
導入優化(我不信!)
用import導入文件的話,每次調用的時候都會去環境變量中查找一次該文件。如果多次調用,那麽會降低程序運行的效率。
用from import後,直接導入了函數的代碼,每次調用就不必再去環境變量中查找變量了,這樣程序運行效率就會高一點
包、導入包
在創建許多模塊後,我們需要將某些功能相近的文件組織在同一文件夾下,這就是一個包。
python包就是,一個有層次的文件目錄結構,一個包含__init__.py 文件的目錄,該目錄下一定得有這個__init__.py文件和其它模塊或子包。
從包中導入模塊的方法和導入模塊一樣:
import PackageA.SubPackageA.ModuleA # 使用時必須用全路徑名 from PackageA.SubPackageA import ModuleA # 可以直接使用模塊名而不用加上包前綴 from PackageA.SubPackageA.ModuleA import functionA # 直接導入模塊中的函數
也可以用*,但是要這樣用,必須在包下的__init__.py文件中,定義好__all__列表,包的情況這個變量不能省略,否則不能用*
from pacakge import *
另外直接import一個包,是不會導入包下的模塊的。
import PackageA
除非你編輯一下PackageA下的__init__.py文件,文件裏加一行
from . import ModuleA
上面的.表示__init__.py所在的當前目錄,也可以用..表示上一級目錄。import不能用*(定義了__all__也沒用),必須指定模塊名。調用的時候還是要Packagea.ModuleA.functionA(),因為上面那句是加在__init__裏的。
但是還是不要這麽用了,上課也沒完全講清楚。
import 的本質
import的本質就是,把導入的模塊運行一遍。
一般這個模塊下都是定義的變量和函數,所以並不會直接運行這些函數,但是如果模塊中有可運行的代碼,也是會在import的時候被運行的
import包的本質就是,運行包下的__init__.py這個文件
__init__.py文件可以為空,一般為空。其實為空的話,也可以幹脆不要這個文件。不過有可以文件做標記可以把這個文件夾和普通的文件夾區分開來。有這個文件,它就是一包。
如果裏面有內容,那麽會在import 或者 from 這個包的時候,執行裏面的內容。這種情況:import PackageA.SubPackageA.ModuleA 會執行每一層包裏的每一個__init__.py文件
但是並不是每次import都會執行一編模塊或者__init__,只有在第一次import的時候才會執行。
__name__ 變量
要讓你的文件既可以被作為包調用,又可以直接運行,但是不要在被調用的時候運行起來,需要用到__name__這個變量。
如果一個文件被作為程序運行的時候,__name__ == ‘__main__‘
如果是作為模塊被調用的時候,__name__ == ‘模塊名‘ or ‘包名.模塊名‘,取決於模塊的位置
一般我們只需要判斷是不是‘__main__‘。加上if判斷,保證你文件中的這段代碼在文件被作為模塊import的時候,不會被執行。
經常使用if __name__ == ‘__main__‘
,保證你寫的文件既可以被import又可以獨立運行,用於test。
內置模塊
time模塊
time.time() # 返回時間戳
time.sleep(n) # 停止n秒
time.gmtime(seconds) #返回一個元祖,UTC時間。參數是時間戳,默認當前時間即time.time()。一般都需要本地時間,看下面一個
時間戳和時間元祖的互相轉換:
time.localtime(seconds) # 時間戳轉為元祖
time.mktime(tuple) # 元祖轉為時間戳
時間元祖和時間字符串的互相轉換:
time.strftime(format,tuple) # format是字符串的格式,後面的元祖可以省略,省略就是當前時間
time.strptime(string,format) # string是表示時間的字符串,format是這個字符串的格式
下面2個函數是分別將元祖和時間戳轉為字符串,字符串格式不能自定義,只能是這種格式:‘Sat Jun 06 16:26:11 1998‘
time.asctime(tuple) # 缺省元祖就是當前時間
time.ctime(seconds) # 缺省時間戳就是當前時間,所以在缺省參數的情況下,和上面的結果一樣
datetime模塊
這個主要就將了2個函數:
import datetime print(datetime.datetime.now()) # 獲取當前時間 print(datetime.timedelta(3)) # 先看一下效果,不是這麽用的 print(datetime.timedelta(2,3,4,5,6,7,1)) # 一共7個參數,天、微妙、毫秒、秒、分、小時、周
timedelta一般是結合now來計算一個過去或者將來的時間的:
import datetime print(datetime.datetime.now()) # 獲取當前時間 print(datetime.datetime.now() + datetime.timedelta(3)) # 3天後的時間 print(datetime.datetime.now() + datetime.timedelta(-3)) # 3天前的時間 print(datetime.datetime.now() + datetime.timedelta(hours=4)) # 4小時後的時間 print(datetime.datetime.now() + datetime.timedelta(minutes=5)) # 5分鐘後的時間
random模塊
import random print(random.random()) # 生成一個[0,1)範圍的隨機浮點數 print(random.uniform(1,2)) # 基本和上面一樣,但是有參數可以指定區間 print(random.randint(1,3)) # 生成一個隨機整數。如果a是參數1,b是參數2,結果是n,則a<=n<=b print(random.randrange(1,7,2)) # 參數和range()一樣,分別是開始、結束、步長,同樣也不包括結束的值 print(random.choice([1,2,3,4,5])) # 參數可以是字符串、列表、元祖這樣的序列 print(random.sample([1,2,3,4,5],3)) # 參數1和上面一樣,參數2表示取多少位。如果參數2等於長度,那麽結果也是隨機排序的
最後還有一個洗牌的函數:
import random list1 = [1,2,3,4,5] print(list1) random.shuffle(list1) # 將參數裏的變量隨機排序了 print(list1) # 看新的結果
random可以用來生成隨機驗證碼,下面的例子只是應用一下這個模塊,驗證碼功能還不夠好:
import random checkcode = ‘‘ for i in range(4): current = random.randint(97,122) # ASCII表中這個範圍是小寫字母 checkcode = "%s%s"%(checkcode,chr(current)) # 用chr將數字根據ASCII轉成字母 print(checkcode)
os模塊
提供對操作系統進行調用的接口
os.getcwd() 獲取當前工作目錄,即當前python腳本工作的目錄路徑
- os.chdir("dirname") 改變當前腳本工作目錄;相當於shell下cd
- os.curdir 返回當前目錄: (‘.‘)
- os.pardir 獲取當前目錄的父目錄字符串名:(‘..‘)
- os.makedirs(‘dirname1/dirname2‘) 可生成多層遞歸目錄
- os.removedirs(‘dirname1‘) 若目錄為空,則刪除,並遞歸到上一級目錄,如若也為空,則刪除,依此類推
- os.mkdir(‘dirname‘) 生成單級目錄;相當於shell中mkdir dirname
- os.rmdir(‘dirname‘) 刪除單級空目錄,若目錄不為空則無法刪除,報錯;相當於shell中rmdir dirname
- os.listdir(‘dirname‘) 列出指定目錄下的所有文件和子目錄,包括隱藏文件,並以列表方式打印
- os.remove() 刪除一個文件
- os.rename("oldname","newname") 重命名文件/目錄
- os.stat(‘path/filename‘) 獲取文件/目錄信息
- os.sep 輸出操作系統特定的路徑分隔符,win下為"\\",Linux下為"/"
- os.linesep 輸出當前平臺使用的行終止符,win下為"\t\n",Linux下為"\n"
- os.pathsep 輸出用於分割文件路徑的字符串
- os.name 輸出字符串指示當前使用平臺。win->‘nt‘; Linux->‘posix‘
- os.system("bash command") 運行shell命令,直接顯示
- os.environ 獲取系統環境變量
- os.path.abspath(path) 返回path規範化的絕對路徑
- os.path.split(path) 將path分割成目錄和文件名二元組返回
- os.path.dirname(path) 返回path的目錄。其實就是os.path.split(path)的第一個元素
- os.path.basename(path) 返回path最後的文件名。如何path以/或\結尾,那麽就會返回空值。即os.path.split(path)的第二個元素
- os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
- os.path.isabs(path) 如果path是絕對路徑,返回True
- os.path.isfile(path) 如果path是一個存在的文件,返回True。否則返回False
- os.path.isdir(path) 如果path是一個存在的目錄,則返回True。否則返回False
- os.path.join(path1[, path2[, ...]]) 將多個路徑組合後返回,第一個絕對路徑之前的參數將被忽略
- os.path.getatime(path) 返回path所指向的文件或者目錄的最後存取時間
- os.path.getmtime(path) 返回path所指向的文件或者目錄的最後修改時間
sys模塊
- sys.argv 命令行參數List,第一個元素是程序本身路徑
- sys.exit(n) 退出程序,正常退出時exit(0)
- sys.version 獲取Python解釋程序的版本信息
- sys.maxint 最大的Int值
- sys.path 返回模塊的搜索路徑,初始化時使用PYTHONPATH環境變量的值
- sys.platform 返回操作系統平臺名稱
sys.stdout:平時用的print實際就是通過sys.stdout來輸出的
import sys sys.stdout.write("Hello") sys.stdout.write("Hello\n") # 這句和下面的print是一樣的 print("Hello") # print實際也是在字符串後面加上換行後,再調用stdout.write
sys.stdin:平時用的input實際上就是先print一段內容,然後再捕獲屏幕上的輸入:
import sys val = input(‘Hello‘) # 上面的和下面的是一樣的 print(‘Hello‘) # 這裏的效果有的差別,print之後有個換行 val = sys.stdin.readline()[:-1] # 這裏切掉了你最後敲的那個回車符
sys.stdout主要是可以用來重定向輸出。可以從控制臺重定向到文件,或者同時定向到控制臺和文件(這個貌似有點復雜),實現日誌記錄。
同樣的sys.stdin和sys.stderr也應該可以重定向,
shutil模塊
高級的文件、文件夾、壓縮包處理模塊
shutil.copyfileobj(fsrc, fdst[, length])
將文件內容拷貝到另一個文件中。這裏只是拷貝文件的內容,所以要把2個文件先打開。參數1和參數2是這兩個文件的句柄。
import shutil with open(‘test.txt‘,encoding=‘utf-8‘) as f1, open(‘test2.txt‘,‘w‘,encoding=‘utf-8‘) as f2: shutil.copyfileobj(f1,f2)
shutil.copyfile(src, dst)
拷貝文件,這個比較簡單,直接輸入原文件名和目標文件名就可以了
import shutil shutil.copyfile(‘test.txt‘,‘test3.txt‘)
shutil.copymode(src, dst)
僅拷貝權限。內容、組、用戶均不變。就是linux裏的chmod的權限(ugo權限)
shutil.copystat(src, dst)
拷貝狀態的信息,包括:mode bits, atime, mtime, flags
shutil.copy(src, dst)
拷貝文件和權限。就是copyfile,然後copymod
shutil.copy2(src, dst)
拷貝文件和狀態信息。就是copyfile,然後copystat
shutil.ignore_patterns(*patterns)
這個略...
shutil.copytree(src, dst, symlinks=False, ignore=None)
遞歸的去拷貝文件
src:原目錄
dst:目標目錄
symlinks:這個默認就好,是處理鏈接的情況,目標目錄裏仍然是鏈接。如果改成True應該會把鏈接的文件拷貝過去
ignore:忽略哪些文件,讓我想到了自動備份,有些擴展名或者目錄我是不需要拷貝的。
shutil.rmtree(path[, ignore_errors[, onerror]])
遞歸的去刪除文件
shutil.move(src, dst)
遞歸的移動文件
shutil.make_archive(base_name, format,...)
創建壓縮包並返回文件路徑
base_name: 壓縮包的文件名,也可以是壓縮包的路徑。只是文件名時,則保存至當前目錄,否則保存至指定路徑,
如:www =>保存至當前路徑
如:/Users/wupeiqi/www =>保存至/Users/wupeiqi/format: 壓縮包種類,“zip”, “tar”, “bztar”,“gztar”
root_dir: 要壓縮的文件夾路徑(默認當前目錄)
owner: 用戶,默認當前用戶
group: 組,默認當前組
logger: 用於記錄日誌,通常是logging.Logger對象
import shutil res = shutil.make_archive(‘test‘,‘gztar‘,‘TXT‘) print(res)
當前文件夾下有一個名為TXT的文件夾,將這個文件夾打包壓縮,在當前目錄下生成了一個test.tar.gz的壓縮文件
json 和 pickle 模塊
用戶序列化的兩個模塊,之前的課已經將過了
json,用於字符串 和 python數據類型間進行轉換
pickle,用於python特有的類型 和 python的數據類型間進行轉換
兩個模塊都提供了名字一樣的四個功能,dumps、dump、loads、load,效果也差不多(適用範圍不同)。
shelve模塊
一個簡單的key,value將內存數據通過文件持久化的模塊,可以持久化任何pickle可支持的python數據格式
直接抄個例子吧。
import shelve d = shelve.open(‘shelve_test‘) #打開一個文件 class Test(object): def __init__(self,n): self.n = n t = Test(123) t2 = Test(123334) name = ["alex","rain","test"] d["test"] = name #持久化列表 d["t1"] = t #持久化類 d["t2"] = t2 d["str1"] = "abc" d["int1"] = 123 d.close()
取回數據:
接著上面的例子,把數據都取回。另外可以用d.items()取回全部。
import shelve d = shelve.open(‘shelve_test‘) #打開一個文件 print(d.get("test")) print(d.get("str1")) print(d.get("int1")) d.close()
xml模塊
古時候用的,以前沒有JSON,現在有了,遇到了再去查吧
PyYAML
這不是一個標準庫,需要的時候還得去下載。處理yaml文檔格式,這種格式主要是做配置文件用的
參考文檔:http://pyyaml.org/wiki/PyYAMLDocumentation
configparser模塊
用於生產和修改配置文檔,一般是ini擴展名。有時候擴展名也可能是.cfg、.conf、.txt,文本格式就是個純文本文件,只是文本內容要遵循一定的格式。
這也是一種常見的格式,來看一個好多軟件的常見文檔格式如下:
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes
[bitbucket.org]
User = hg
[topsecret.server.com]
Port = 50022
ForwardX11 = no
下面用代碼來生成一個這樣的配置文件:
import configparser config = configparser.ConfigParser() config[‘DEFAULT‘] = {‘ServerAliveInterval‘:‘45‘, ‘Compression‘:‘yes‘, ‘CompressionLevel‘:9} # 字典裏的數字可以不加引號,不過到文件裏肯定還是一樣的 config[‘bitbucket.org‘] ={} # 這裏也可以先建個空的,然後再定義裏面的值 config[‘bitbucket.org‘][‘User‘] = ‘hg‘ config[‘topsecret.server.com‘] = {} topsecret = config[‘topsecret.server.com‘] # 每次的要打一串也很煩,所以這麽弄 topsecret[‘Host Port‘] = ‘50022‘ # 這裏必須是字符串,因為不支持數字格式 topsecret[‘ForwardX11‘] = ‘no‘ config[‘DEFAULT‘][‘ForwardX11‘] = ‘yes‘ with open(‘test.ini‘,‘w‘) as inifile: config.write(inifile)
下面用代碼來取出配置:
import configparser config = configparser.ConfigParser() print(config.sections()) # 現在還是空的 config.read(‘test.ini‘) # 讀取配置文件,保存在config裏 print(config.defaults()) print(config.sections()) # 打印節點,讀到了2個,沒有DEFAULT,也沒有下面的詳細內容 print(config[‘bitbucket.org‘][‘user‘]) # 讀取具體的某一條配置信息 topsecret = config[‘topsecret.server.com‘] # 還是嫌名字太長,很麻煩,就這麽弄 print(topsecret[‘forwardx11‘]) for key in config[‘bitbucket.org‘]: # 節點會繼承DEFAULT的屬性,沒有就繼承,有就替代 print(key) # bitbucket.org下面值定義了一個屬性,但是這裏繼承了DEFAULT的所有屬性 print(config[‘bitbucket.org‘][‘forwardx11‘]) # 這裏繼承了DEFAULT的屬性,所以有值,是yes
當然還可以對配置文件進行增刪改查,真要改的時候再說吧,一般都是手動修改的。
hashlib模塊
用於加密相關的操作,3.x裏代替了md5模塊和sha模塊,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512, MD5 算法。所以上面這些格式用這個就好了。
import hashlib m = hashlib.md5() # 別的格式也一樣,只要替換這裏的名字就好。比如:hashlib.sha1() m.update(b‘Hello‘) print(m.digest()) # 2進制格式hash,也有16進制的方法 m.update(b"It‘s me") # update()是繼續往裏追加 print(m.digest()) m2 = hashlib.md5() m2.update(b"HelloIt‘s me") print(m2.digest()) # 這裏的輸出應該和上面一樣 print(m2.hexdigest()) # 16進制格式hash,貌似16進制用的多
如果要進行運算的不是ascii碼,就需要用encode轉成bytes數據類型
import hashlib m = hashlib.sha512() # 試個sha512的 m.update(‘你好‘.encode(encoding=‘utf-8‘)) # 數據類型必須是bytes print(m.hexdigest())
下面這個是可以進一步加密的模塊,更高的安全性。
hmac模塊
散列消息鑒別碼,簡稱HMAC,是一種基於消息鑒別碼MAC(Message Authentication Code)的鑒別機制。使用HMAC時,消息通訊的雙方,通過驗證消息中加入的鑒別密鑰Key來鑒別消息的真偽。
一般用於網絡通信中消息加密,前提是雙方先要約定好key,就像接頭暗號一樣,然後消息發送方用key把消息加密,接收方用key + 消息明文再加密,拿加密後的值跟發送者的相對比是否相等,這樣就能驗證消息的真實性,及發送者的合法性了。
假設約定的key是“一二三四五”,我們要發一條消息“上山打老虎”:
import hmac # 可以直接把key和消息一起生成hash h_send= hmac.new("一二三四五".encode(encoding=‘utf-8‘),"上山打老虎".encode(encoding=‘utf-8‘)) print(h_send.hexdigest()) # 也可以先把key導入 h_receive = hmac.new("一二三四五".encode(encoding=‘utf-8‘)) # 然後再用update()方法,把消息導入,生成hash h_receive.update("上山打老虎".encode(encoding=‘utf-8‘)) print(h_receive.hexdigest()) # 和上面的hash值是一樣的 # 還有第三個參數,上面缺省了,默認是md5格式,如果要用別的格式,就補上參數 h_send= hmac.new("一二三四五".encode(encoding=‘utf-8‘),"上山打老虎".encode(encoding=‘utf-8‘),‘sha1‘) # 使用sha1加密 print(h_send.hexdigest()) h_receive = hmac.new("一二三四五".encode(encoding=‘utf-8‘),digestmod=‘sha1‘) # 這裏沒有第二個參數,貌似只能用關鍵參數 h_receive.update("上山打老虎".encode(encoding=‘utf-8‘)) print(h_receive.hexdigest()) # 和上面的hash值是一樣的
hmac的應用:
hmac主要應用在身份驗證是,它的使用方法是這樣的:
客戶端發出登錄請求(假設是瀏覽器的GET請求)
服務器返回一個隨機值,並在會話中記錄這個隨機值
客戶端將該隨機值作為密鑰,用戶密碼進行hmac運算,然後提交給服務器
服務器讀取用戶數據庫中的用戶密碼和步驟2中發送的隨機值做與客戶端一樣的hmac運算,然後與用戶發送的結果比較,如果結果一致則驗證用戶合法
散列算法僅適用於登錄驗證,但是對於最初的密碼設置和以後密碼修改的過程不適用。但是散列算法要比對稱和非對稱加密算法效率高。
加密的總結:上面的2個加密模塊,應該都不是用來加密傳數據的,因為加密後並不能解密,只是生成一個消息摘要,用來驗證消息的完整性的。也可以理解為對消息生成一個數字簽名,如果簽名一致,則認為消息沒有被修改過。
subprocess模塊
運行linux的shelll命令,管理子進程。是對這些命令的替換 os.system 和 os.spawn* 。所以盡量用subprocess。沒有展開講
logging模塊
用來記錄日誌的,這個很有用,也很重要。
日誌分為5個級別,重要等級一次降低是:critical、error、warning、info、debug
簡單的例子:
import logging logging.basicConfig(filename=‘test.log‘,level=logging.INFO) # 沒有位置參數,必須用關鍵參數 logging.warning(‘test warning‘) logging.info(‘test info‘) logging.debug(‘test debug‘)
去看一下文件的內容,應該只有2行。因為參數level設置了只接收info等級及以上的日誌,所以debug不會記錄下來。
另外日誌內容也很少,沒有時間。有更加詳細的參數可以定義日誌的格式
日誌格式:
%(name)s | Logger的名字 |
%(levelno)s | 數字形式的日誌級別 |
%(levelname)s | 文本形式的日誌級別 |
%(pathname)s | 調用日誌輸出函數的模塊的完整路徑名,可能沒有 |
%(filename)s | 調用日誌輸出函數的模塊的文件名 |
%(module)s | 調用日誌輸出函數的模塊名 |
%(funcName)s | 調用日誌輸出函數的函數名 |
%(lineno)d | 調用日誌輸出函數的語句所在的代碼行 |
%(created)f | 當前時間,用UNIX標準的表示時間的浮 點數表示 |
%(relativeCreated)d | 輸出日誌信息時的,自Logger創建以 來的毫秒數 |
%(asctime)s | 字符串形式的當前時間。默認格式是 “2003-07-08 16:49:45,896”。逗號後面的是毫秒 |
%(thread)d | 線程ID。可能沒有 |
%(threadName)s | 線程名。可能沒有 |
%(process)d | 進程ID。可能沒有 |
%(message)s | 用戶輸出的消息 |
下面的例子選了那些比較有用的格式,有日期、時間、模塊名、代碼行、日誌級別和消息。
logging.basicConfig(filename=‘test.log‘, level=logging.DEBUG, format=‘%(asctime)s %(module)s-%(lineno)d [%(levelname)s]:%(message)s‘, datefmt=‘%Y-%m-%d %H:%M:%S‘) logging.warning(‘test warning‘) logging.info(‘test info‘) logging.debug(‘test debug‘)
默認的設置就很好用,但是只能輸出到文件。如果想同時把log同時打印在屏幕上和文件裏,需要自己創建一個logger。貼一點基礎知識
Python 使用的logging模塊記錄日誌涉及四個主要類,使用官方文檔中的概括最為合適:
logger提供了應用程序可以直接使用的接口;
handler將(logger創建的)日誌記錄發送到合適的目的輸出;
filter提供了細度設備來決定輸出哪條日誌記錄;
formatter決定日誌記錄的最終輸出格式。
每個類的介紹就不貼了,直接上例子:
import logging # 先創建一個logger logger = logging.getLogger(__name__) # 定義Logger的名字,之前直接用logging調用的名字是root,日誌格式用%(name)s可以獲得。這裏的名字也可以自定義比如"TEST" logger.setLevel(logging.DEBUG) # 低於這個級別將被忽略,後面還可以設置輸出級別 # 創建handler和輸出級別 ch = logging.StreamHandler() # 輸出到屏幕的handler ch.setLevel(logging.INFO) # 輸出級別和上面的忽略級別都不一樣,可以看一下效果 fh = logging.FileHandler(‘access.log‘,encoding=‘utf-8‘) # 輸出到文件的handler,定義一下字符編碼 fh.setLevel(logging.WARNING) # 創建日誌格式,可以為每個handler創建不同的格式 ch_formatter = logging.Formatter(‘%(name)s %(asctime)s {%(levelname)s}:%(message)s‘,datefmt=‘%Y-%m-%d %H:%M:%S‘) # 關鍵參數datefmt自定義日期格式 fh_formatter = logging.Formatter(‘%(asctime)s %(module)s-%(lineno)d [%(levelname)s]:%(message)s‘,datefmt=‘%Y/%m/%d %H:%M:%S‘) # 把上面的日誌格式和handler關聯起來 ch.setFormatter(ch_formatter) fh.setFormatter(fh_formatter) # 將handler加入logger logger.addHandler(ch) logger.addHandler(fh) # 以上就完成了,下面來看一下輸出的日誌 logger.debug(‘logger test debug‘) logger.info(‘logger test info‘) logger.warning(‘logger test warning‘) logger.error(‘logger test error‘) logger.critical(‘logger test critical‘)
上面這個例子據說能滿足90%的需求了。還需要一個日誌文件輪訓的功能。只需要用另外一個模塊重新定義一個fh就好了,就改1句。
import logging from logging import handlers # 需要額外導入這個模塊, # 還是要創建logger,這裏不是必須的設置都省略了 logger = logging.getLogger(__name__) #fh = logging.FileHandler(‘access.log‘,encoding=‘utf-8‘) # 原來的代碼,替換為下面2種,一個是看時間,一個是看大小 #fh = handlers.TimedRotatingFileHandler(filename=‘access.log‘,when="S",interval=5,backupCount=3) fh = handlers.RotatingFileHandler(filename=‘access.log‘,encoding=‘utf-8‘,maxBytes=100,backupCount=3) fh_formatter = logging.Formatter(‘%(asctime)s %(module)s-%(lineno)d [%(levelname)s]:%(message)s‘,datefmt=‘%Y/%m/%d %H:%M:%S‘) fh.setFormatter(fh_formatter) logger.addHandler(fh) # 以上就完成了,多輸出幾次 for i in range(10): logger.critical(‘logger test critical%d‘%i)
參數說明:
interval是時間間隔。
when參數是一個字符串。表示時間間隔的單位,不區分大小寫。它有以下取值:
S 秒
M 分
H 小時
D 天
W 每星期(interval==0時代表星期一)
midnight 每天淩晨,就是每天一個日誌文件,很方便。
maxBytes:用於指定日誌文件的最大文件大小。如果maxBytes為0,意味著日誌文件可以無限大,這時上面描述的重命名過程就不會發生
backupCount:用於指定保留的備份文件的個數。比如,如果指定為2,當上面描述的重命名過程發生時,原有的chat.log.2並不會被更名,而是被刪除。
以上號稱是能滿足95%的需求了(我信了!)。剩下的是日誌過濾,要用到四個類裏的filter,號稱很復雜,且用的不多,就沒講。
re模塊
正則表達式,很重要的模塊。
常用的正則表達式符號
字符 | 描述 |
---|---|
‘.‘ | 匹配除 "\n" 之外的任何單個字符。若指定flag DOTALL,則匹配任意字符,包括換行。 |
‘^‘ | 匹配輸入字符串的開始位置。若指定flags MULTILINE,^ 也匹配 ‘\n‘ 或 ‘\r‘ 之後的位置。如:("^a","\nabc\neee",flags=re.MULTILINE) |
‘$‘ | 匹配輸入字符串的結束位置。若指定flags MULTILINE,$ 也匹配 ‘\n‘ 或 ‘\r‘ 之前的位置。 |
‘*‘ | 匹配前面的子表達式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等價於{0,}。 |
‘+‘ | 匹配前面的子表達式一次或多次。例如,‘zo+‘ 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價於 {1,}。 |
‘?‘ | 匹配前面的子表達式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等價於 {0,1}。 |
‘{n}‘ | n 是一個非負整數。匹配確定的 n 次。例如,‘o{2}‘ 不能匹配 "Bob" 中的 ‘o‘,但是能匹配 "food" 中的兩個 o。 |
‘{n,}‘ | n 是一個非負整數。至少匹配n 次。例如,‘o{2,}‘ 不能匹配 "Bob" 中的 ‘o‘,但能匹配 "foooood" 中的所有 o。‘o{1,}‘ 等價於 ‘o+‘。‘o{0,}‘ 則等價於 ‘o*‘。 |
‘{n,m}‘ | m 和 n 均為非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 將匹配 "fooooood" 中的前三個 o。‘o{0,1}‘ 等價於 ‘o?‘。請註意在逗號和兩個數之間不能有空格。 |
‘?‘ | 當該字符緊跟在任何一個其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 後面時,匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對於字符串 "oooo",‘o+?‘ 將匹配單個 "o",而 ‘o+‘ 將匹配所有 ‘o‘。 |
‘x|y‘ | 匹配 x 或 y。例如,‘z|food‘ 能匹配 "z" 或 "food"。‘(z|f)ood‘ 則匹配 "zood" 或 "food"。 |
‘[xyz]‘ | 字符集合。匹配所包含的任意一個字符。例如, ‘[abc]‘ 可以匹配 "plain" 中的 ‘a‘。 |
‘[^xyz]‘ | 負值字符集合。匹配未包含的任意字符。例如, ‘[^abc]‘ 可以匹配 "plain" 中的‘p‘、‘l‘、‘i‘、‘n‘。 |
‘[a-z]‘ | 字符範圍。匹配指定範圍內的任意字符。例如,‘[a-z]‘ 可以匹配 ‘a‘ 到 ‘z‘ 範圍內的任意小寫字母字符。 |
‘[^a-z]‘ | 負值字符範圍。匹配任何不在指定範圍內的任意字符。例如,‘[^a-z]‘ 可以匹配任何不在 ‘a‘ 到 ‘z‘ 範圍內的任意字符。 |
‘\d‘ | 匹配一個數字字符。等價於 [0-9]。 |
‘\D‘ | 匹配一個非數字字符。等價於 [^0-9]。 |
‘\w‘ | 匹配字母、數字、下劃線。等價於‘[A-Za-z0-9_]‘。 |
‘\W‘ | 匹配非字母、數字、下劃線。等價於 ‘[^A-Za-z0-9_]‘。 |
‘\A‘ | 匹配字符開頭,類似^,必須是字符串的開頭,無法匹配‘\n‘之後的位置,忽略flags MULTILINE |
‘\Z‘ | 匹配字符結尾,類似$,必須是字符串的結尾,無法匹配‘\n‘之前的位置,忽略flags MULTILINE |
‘\s‘ | 匹配任何空白字符,包括空格、制表符、換頁符等等。等價於 [ \f\n\r\t\v]。 |
‘\S‘ | 匹配任何非空白字符。等價於 [^ \f\n\r\t\v]。 |
‘\t‘ | 匹配一個制表符。等價於 \x09 和 \cI。 |
‘\v‘ | 匹配一個垂直制表符。等價於 \x0b 和 \cK。 |
‘\f‘ | 匹配一個換頁符。等價於 \x0c 和 \cL。 |
‘\n‘ | 匹配一個換行符。等價於 \x0a 和 \cJ。 |
‘\r‘ | 匹配一個回車符。等價於 \x0d 和 \cM。 |
分組匹配:
‘(...)‘ : 需要用group(),返回元祖
‘(?P<name>...)‘ :需要用groupdict(),返回字典。這裏的‘name‘替換成你要定義的字典的Key
幾個匹配模式:
re.I (IGNORECASE):忽略大小寫
re.M(MULTLINE) :多行模式,主要影響^和$的匹配
re.S(DOTALL) :點任意匹配模式,影響(.)點的匹配
上面的括號內是全拼,可以re.I這麽寫,也可以re.IGNORECASE這麽寫
常用的匹配語法:
re.match:從頭開始匹配
re.search:匹配包含,一般都是用這個
re.findall:把所有匹配到的字符放到列表中,以列表中的元素返回
re.split:以匹配到的字符當做分隔符,返回列表
re.sub:匹配字符並替換
例子:
match 和 search
import re test_match = re.match(‘abc‘,‘aabcde‘) # 從頭開始匹配 test_search = re.search(‘abc‘,‘aabcde‘) # 匹配包含 print(test_match) # 匹配不到,因為不是以abc開頭 #print(test_match.group()) # 這句會報錯,匹配不到就是None.沒有group屬性 print(test_search) # 匹配到了,包含abc字符串 print(test_search.group()) # 要返回匹配到的字符串使用group()
^ 、$ 和 \A、\Z 以及多行模式
在沒有換行的情況下,兩個的作用是一樣的,就看一下有換行的情況。
import re string1 = ‘abc\ndef‘ a1 = re.search(‘^def‘,string1) print(a1) # 不開啟多行模式,匹配不到 b1 = re.search(‘^def‘,string1,re.M) print(b1) # 開啟多行模式才能匹配到 c1 = re.search(‘\Adef‘,string1) print(c1) d1 = re.search(‘\Adef‘,string1,re.M) print(d1) # 用\A會忽略多行模式,都是匹配不到的 string2 = ‘abc\ndef\n‘ a2 = re.search(‘def$‘,string2) print(a2) # 這種有個換行符結尾的情況,$可以匹配到 b2 = re.search(‘def\Z‘,string2) print(b2) # 這種有個換行符結尾的情況,\Z就匹配不到 c2 = re.search(‘abc$‘,string2) print(c2) d2 = re.search(‘abc$‘,string2,re.M) print(d2) # 不過不是最後一個換行符,需要開啟多行模式才能匹配
分組匹配
舉一個身份證號碼的例子
import re id = ‘姓名:XXX 身份證號碼:31010119990919935x 籍貫:XXX 職業:XXX‘ a = re.search(‘(\d{6})(\d{4})(\d{2})(\d{2})\d{3}(\d|x)‘,id) print(a) print(a.group()) # 只是把身份證號碼提取出來 print(a.groups()) # 這裏實現了分組,分別是地址碼、年、月、日,中間3我沒用小括號,性別。
上面是元祖的形式返回,還可以用字典返回,需要定義Key
import re id = ‘姓名:XXX 身份證號碼:31010119990919935x 籍貫:XXX 職業:XXX‘ a = re.search(‘(?P<city>\d{6})(?P<Year>\d{4})(?P<Month>\d{2})(?P<Day>\d{2})\d{3}(?P<Sex>\d|x)‘,id) print(a.groups()) # 用group輸出還是一樣 print(a.groupdict()) # 要用groupdict看字典
findall 和 split
import re string = ‘abc123abc123x0y9z8‘ test_find = re.findall(‘\d+‘,string) # ‘\d+‘是匹配連續的數字 test_split = re.split(‘\d+‘,string) print(test_find) # 返回了所有的數字組合 print(test_split) # 把所有的數字作為分隔符,相當於返回了所有的字母組合。由於split的特性,最後是數字結尾的,最後會有一個空字符元素 test_find2 = re.findall(‘[^\d]+‘,string) # [^]是對中括號這的字符集合取反 print(test_find2) # 和上面一樣,返回了所有的非數字組合
sub 匹配並替換
sub(pattern, repl, string, count=0, flags=0)
pattern:要匹配的正則表達式
repl:要替換的字符串
string:帶匹配和替換的字符串
count:匹配和替換的次數,默認0全部匹配
flags:3種匹配模式,默認不開啟
import re string = r"C:\Users\Public\Pictures\test.jpg" string2 = re.sub(r‘\\‘,r‘/‘,string) # 將\替換成/ print(string2) string3 = re.sub(r‘\\‘,r‘/‘,string,2) # 規定只替換2次,默認是0全部替換 print(string3) text = "Alex is a goodboy , he is coll , clever , and so on..." text2 = re.sub(‘\s+,\s+‘,‘,‘,text) # 把字符串中(,)逗號前後的空字符都去掉 print(text) print(text2)
作業
作業一:ATM+購物商城程序
額度 15000或自定義
實現購物商城,買東西加入購物車,調用信用卡接口結賬
可以提現,手續費5%
支持多賬戶登錄
支持賬戶間轉賬
記錄每月日常消費流水
提供還款接口
ATM記錄操作日誌
提供管理接口,包括添加賬戶、用戶額度,凍結賬戶等。。。
用戶認證用裝飾器
補充說明:
使用軟件開發目錄規範來組織目錄結構,存放代碼、配置文件和數據
用戶認證要用裝飾器,驗證登錄狀態要在每一個方法裏都能用,這裏要用裝飾器。登錄成功後會生成一個用戶信息的全局變量,每次只要去調用一個這個全局變量就能驗證用戶的登錄狀態
購物商城之前的作業已經做過,可以直接拿來稍微改一下後使用
作業二:模擬計算器開發
實現加減乘除及拓號優先級解析
用戶輸入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )等類似公式後,必須自己解析裏面的(),+,-,*,/符號和公式(不能調用eval等類似功能偷懶實現),運算後得出結果,結果必須與真實的計算器所得出的結果一致
補充說明:
練習正則的使用
先用正則找到()裏的內容,把()計算出來替換掉
然後按級別,再解析乘除的運算
最後解析計算加減
Python自動化開發學習5