1. 程式人生 > >Python自動化開發學習5

Python自動化開發學習5

python

模塊

在模塊中,我們可以定義變量、函數,還有類(這個還沒學到)。總之應該就是所有的代碼。先建一個文件,取名為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主要應用在身份驗證是,它的使用方法是這樣的:

  1. 客戶端發出登錄請求(假設是瀏覽器的GET請求)

  2. 服務器返回一個隨機值,並在會話中記錄這個隨機值

  3. 客戶端將該隨機值作為密鑰,用戶密碼進行hmac運算,然後提交給服務器

  4. 服務器讀取用戶數據庫中的用戶密碼和步驟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模塊記錄日誌涉及四個主要類,使用官方文檔中的概括最為合適:

  1. logger提供了應用程序可以直接使用的接口;

  2. handler將(logger創建的)日誌記錄發送到合適的目的輸出;

  3. filter提供了細度設備來決定輸出哪條日誌記錄;

  4. 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. 用戶認證要用裝飾器,驗證登錄狀態要在每一個方法裏都能用,這裏要用裝飾器。登錄成功後會生成一個用戶信息的全局變量,每次只要去調用一個這個全局變量就能驗證用戶的登錄狀態

  3. 購物商城之前的作業已經做過,可以直接拿來稍微改一下後使用

作業二:模擬計算器開發

實現加減乘除及拓號優先級解析

用戶輸入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )等類似公式後,必須自己解析裏面的(),+,-,*,/符號和公式(不能調用eval等類似功能偷懶實現),運算後得出結果,結果必須與真實的計算器所得出的結果一致

補充說明:

  1. 練習正則的使用

  2. 先用正則找到()裏的內容,把()計算出來替換掉

  3. 然後按級別,再解析乘除的運算

  4. 最後解析計算加減


Python自動化開發學習5