第五周学习笔记
python 軟件目錄開發規范
為什么要設計好目錄結構?
"設計項目目錄結構",就和"代碼編碼風格"一樣,屬于個人風格問題。對于這種風格上的規范,一直都存在兩種態度:
我是比較偏向于后者的,因為我是前一類同學思想行為下的直接受害者。我曾經維護過一個非常不好讀的項目,其實現的邏輯并不復雜,但是卻耗費了我非常長的時間去理解它想表達的意思。從此我個人對于提高項目可讀性、可維護性的要求就很高了。"項目目錄結構"其實也是屬于"可讀性和可維護性"的范疇,我們設計一個層次清晰的目錄結構,就是為了達到以下兩點:
所以,我認為,保持一個層次清晰的目錄結構是有必要的。更何況組織一個良好的工程目錄,其實是一件很簡單的事兒。
目錄組織方式
關于如何組織一個較好的Python工程目錄結構,已經有一些得到了共識的目錄結構。在Stackoverflow的這個問題上,能看到大家對Python目錄結構的討論。
這里面說的已經很好了,我也不打算重新造輪子列舉各種不同的方式,這里面我說一下我的理解和體會。
假設你的項目名為foo, 我比較建議的最方便快捷目錄結構這樣就足夠了:
Foo/ |-- bin/ | |-- foo | |-- foo/ | |-- tests/ | | |-- __init__.py | | |-- test_main.py | | | |-- __init__.py | |-- main.py | |-- docs/ | |-- conf.py | |-- abc.rst | |-- setup.py |-- requirements.txt |-- README簡要解釋一下:
除此之外,有一些方案給出了更加多的內容。比如LICENSE.txt,ChangeLog.txt文件等,我沒有列在這里,因為這些東西主要是項目開源的時候需要用到。
下面,再簡單講一下我對這些目錄的理解和個人要求吧。
關于README的內容
這個我覺得是每個項目都應該有的一個文件,目的是能簡要描述該項目的信息,讓讀者快速了解這個項目。
它需要說明以下幾個事項:
我覺得有以上幾點是比較好的一個README。在軟件開發初期,由于開發過程中以上內容可能不明確或者發生變化,并不是一定要在一開始就將所有信息都補全。但是在項目完結的時候,是需要撰寫這樣的一個文檔的。
可以參考Redis源碼中Readme的寫法,這里面簡潔但是清晰的描述了Redis功能和源碼結構。
關于requirements.txt和setup.py
setup.py
一般來說,用setup.py來管理代碼的打包、安裝、部署問題。業界標準的寫法是用Python流行的打包工具setuptools來管理這些事情。這種方式普遍應用于開源項目中。不過這里的核心思想不是用標準化的工具來解決這些問題,而是說,一個項目一定要有一個安裝部署工具,能快速便捷的在一臺新機器上將環境裝好、代碼部署好和將程序運行起來。
這個我是踩過坑的。
我剛開始接觸Python寫項目的時候,安裝環境、部署代碼、運行程序這個過程全是手動完成,遇到過以下問題:
setup.py可以將這些事情自動化起來,提高效率、減少出錯的概率。"復雜的東西自動化,能自動化的東西一定要自動化。"是一個非常好的習慣。
setuptools的文檔比較龐大,剛接觸的話,可能不太好找到切入點。學習技術的方式就是看他人是怎么用的,可以參考一下Python的一個Web框架,flask是如何寫的:?setup.py
當然,簡單點自己寫個安裝腳本(deploy.sh)替代setup.py也未嘗不可。
requirements.txt
這個文件存在的目的是:
這個文件的格式是每一行包含一個包依賴的說明,通常是flask>=0.10這種格式,要求是這個格式能被pip識別,這樣就可以簡單的通過?pip install -r requirements.txt來把所有Python包依賴都裝好了。具體格式說明:?點這里。
?
關于配置文件的使用方法
注意,在上面的目錄結構中,沒有將conf.py放在源碼目錄下,而是放在docs/目錄下。
很多項目對配置文件的使用做法是:
這種做法我不太贊同:
所以,我認為配置的使用,更好的方式是,
能夠佐證這個思想的是,用過nginx和mysql的同學都知道,nginx、mysql這些程序都可以自由的指定用戶配置。
所以,不應當在代碼中直接import conf來使用配置文件。上面目錄結構中的conf.py,是給出的一個配置樣例,不是在寫死在程序中直接引用的配置文件。可以通過給main.py啟動參數指定配置路徑的方式來讓程序讀取配置內容。當然,這里的conf.py你可以換個類似的名字,比如settings.py。或者你也可以使用其他格式的內容來編寫配置文件,比如settings.yaml之類的。
?
python在不同層級目錄import模塊的方法使用python進行程序編寫時,經常會使用第三方模塊包。這種包我們可以通過python setup install 進行安裝后,通過import XXX或from XXX import yyy 進行導入。不過如果是自己遍寫的依賴包,又不想安裝到python的相應目錄,可以放到本目錄里進行import進行調用;為了更清晰的理清程序之間的關系,例如我們會把這種包放到lib目錄再調用。本篇就針對常見的模塊調用方法匯總下。
一、同級目錄下的調有
程序結構如下:
-- src
??? |-- mod1.py
??? |-- test1.py
若在程序test1.py中導入模塊mod1, 則直接使用
import mod1
或
from mod1 import *;
二、調用子目錄下的模塊
程序結構如下:
-- src
??? |-- mod1.py
??? |-- lib
??? |??? |-- mod2.py
??? |-- test1.py
這時看到test1.py和lib目錄(即mod2.py的父級目錄),如果想在程序test1.py中導入模塊mod2.py ,可以在lib件夾中建立空文件__init__.py文件(也可以在該文件中自定義輸出模塊接口),然后使用:
from lib.mod2 import *
或
import lib.mod2.
三、調用上級目錄下的文件
程序結構如下:
-- src
??? |-- mod1.py
??? |-- lib
??? |??? |-- mod2.py
??? |-- sub
??? |??? |-- test2.py
這里想要實現test2.py調用mod1.py和mod2.py ,做法是我們先跳到src目錄下面,直接可以調用mod1,然后在lib上當下建一個空文件__init__.py ,就可以像第二步調用子目錄下的模塊一樣,通過import? lib.mod2進行調用了。具體代碼如下:
從一個目錄調用另一個目錄下文件
找到文件根目錄
?
import os,sysBASE_DIR = os.path.dirname(os.path.abspath(__file__)) #os.path.abspath(__file__)為文件絕對路徑 #os.path.dirname為文件目錄。再上級目錄再加一次os.path.dirname,依此類推。。。 sys.path.append(BASE_DIR) #添加環境變量?
python ?import及模塊本質
模塊的定義:
用來從邏輯上組織python代碼(變量,函數,類,邏輯:實現一個功能),本質上就是.py結尾的python文件。
包的定義:
用來從邏輯上組織模塊的,本質上就是一個目錄。(必須有一個__init__.py文件)
導入模塊方法:
1、創建模塊
比如創建一個 module_name.py ,里面可以定義函數和變量。
2、導入模塊
導入一個:
import module_name
導入多個:
import module_name,import module_name1
導入一個模塊所有的方法或變量:
from module_name import *
from module_name import 方法名 [ as 別名]
from module_name import 方法名1 [ as 別名1],方法名2 [ as 別名2].。。。
from..import.. 這種方法比 直接import 模塊 要執行的快,因為不需要點語法調用方法,所以也不需要每次調用每次都去找那個模塊文件。
3、使用模塊的變量或方法
1、直接 import module_name 導入進來的
module_name.變量名 或 module_name.方法名 。這種相當于把 module_name.py中的代碼解釋一遍統一賦值給一個變量。
?
2、from module_name import 方法名 [ as 別名] 這種方法導入進來的
可以直接使用。變量名、方法名、? 別名。這個相當于把代碼復制一份到新文件里面。所以不需要點語法調用。
導入包方法:
import package_name?
導入這個包實際上在執行包里面的__init__.py文件
?
import本質(路徑搜索)
模塊導入:
import module_name -->找到module_name.py文件 --->找到module_name.py的路徑 --->sys.path中(第一個空的是當前目錄)依次尋找。
如果sys.path中沒有要找的路徑,意思是要導入的模塊文件不再同一個目錄,需要通過sys和os模塊把要導入的模塊的路徑加入到sys.path中
1、通過os.path.abspath(__file__) 獲取當前文件路徑。
2、通過os.path.dirname(os.path.abspath(__file__)) 找到上級路徑。直到找到模塊所在的路徑下。
3、追加到sys.path路徑下 sys.path.append(要添加的路徑)。
包導入:
1、首先導入一個包 也是import ,那么實際執行的動作是執行包里的__init__.py這個文件。
2、包里面肯定定義了很多模塊,不然只執行這個文件沒有意義。這個文件的作用就是把模塊的導入寫到這個文件里面。from . import module_name 導入當前路徑下的哪個模塊。
3、調用某個包下的某個模塊下的某個方法:package_name.module_name.方法
4、如果包的目錄和程序的目錄不一致,可以先加載包的最外層目錄到sys.path,然后 from dir_name import package_name
?
?
模塊分類
?
1、模塊:
?
? ? ? ? 定義:其實模塊簡單說就是一堆代碼實現某個功能,它們是已經寫好的.py文件。只需要用import應用即可。
?
? ? ? ? 分類:? ? ??
?
1、自定義模塊
?
2、內置標準模塊(又稱標準庫)
?
3、開源模塊
?
? ?1、自定義模塊,就是自己寫的.py文件為了實現某個功能。
?
? ?2、內置模塊,就是python自身已經寫好的某個功能,例如經常用的sys、os等模塊都是內置模塊。
?
? ?3、開源模塊,開源大家都明白吧,就是不收費的別人寫好的模塊,一般也稱第三方模塊。
?
? ? ? ? 模塊的引用:
?
1、import modules
?
2、from modules import ?函數
?
3、如果有多個模塊,可以用逗號隔開,如果想把某個模塊里面的所有功能都導入,可以用*,這樣的話功能都加載道內存里面,占用內存資源,不建議用。
?
?time &datetime模塊
import time import datetimetime print(time.clock()) #返回處理器時間,3.3以后廢棄 4.444098792316153e-07 print(time.process_time()) #返回處理器時間 0.031200199999999997 print(time.time()) #返回當前系統時間戳 1463472071.3892002 print(time.ctime()) #返回當前系統時間 Tue May 17 16:01:11 2016 print(time.ctime(time.time()-86400)) #轉換成字符串格式 Mon May 16 16:01:11 2016 print(time.gmtime(time.time()-86400)) #將時間戳轉換成struct_time格式 time.struct_time(tm_year=2016, tm_mon=5, tm_mday=16, tm_hour=8, tm_min=1, tm_sec=11, tm_wday=0, tm_yday=137, tm_isdst=0) print(time.localtime(time.time()-86400)) #將時間戳轉換成struct_time格式,本地時間。 time.struct_time(tm_year=2016, tm_mon=5, tm_mday=16, tm_hour=16, tm_min=13, tm_sec=25, tm_wday=0, tm_yday=137, tm_isdst=0) print(time.mktime(time.localtime())) #與time.localtime()功能相反,將struct_time格式轉回成時間戳格式 1463472904.0 time.sleep(4) #sleep 每隔四秒以執行 print(time.strftime("%Y-%m-%d %H:%M:%S",time.gmtime()) ) #將struct_time格式轉成指定的字符串格式 2016-05-17 08:16:22 datetime print(datetime.date.today()) #輸出格式 2016-05-17 print(datetime.date.fromtimestamp(time.time()-86400) ) # 將時間戳轉成日期格式 2016-05-16 current_time = datetime.datetime.now() # print(current_time) #輸出2016-05-17 16:17:59.863200 print(current_time.timetuple()) #返回struct_time格式 time.struct_time(tm_year=2016, tm_mon=5, tm_mday=17, tm_hour=16, tm_min=17, tm_sec=59, tm_wday=1, tm_yday=138, tm_isdst=-1)#datetime.replace([year[, month[, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]]]]]) print(current_time.replace(2016,5,17)) #輸出2016-05-17 16:19:33.753200,返回當前時間,但指定的值將被替換 str_to_date = datetime.datetime.strptime("21/11/06 16:30", "%d/%m/%y %H:%M") #將字符串轉換成日期格式 new_date1 = datetime.datetime.now() + datetime.timedelta(days=10) #比現在加10天 2016-05-27 16:21:16.279200 new_date2 = datetime.datetime.now() + datetime.timedelta(days=-10) #比現在減10天 2016-05-07 16:21:44.459200 new_date3 = datetime.datetime.now() + datetime.timedelta(hours=-10) #比現在減10小時 2016-05-17 06:22:01.299200 new_date4 = datetime.datetime.now() + datetime.timedelta(seconds=120) #比現在+120s 2016-05-17 16:24:10.917200 new_date5 = datetime.datetime.now() + datetime.timedelta(weeks=20) #比現在+10周 2016-10-04 16:23:02.904200 print(new_date5)?
?random
#!/usr/bin/env python #—*—coding:utf-8—*—import random#隨機數 # print(random.random())#0到的隨機數,是一個float浮點數 # print(random.randint(1,9))#一到九隨機 # print(random.randrange(1,10))#一到十隨機(記得range只有頭沒有尾巴) 原型random.randrange([start], stop[, step]) # # 生成隨機驗證碼 # # checkcode='' # for i in range(5): # current = random.randrange(0,5) # if current == i: # tmp = chr(random.randint(65,90))#chr是ascii碼表,65,90是(A-Z) # else: # tmp = random.randint(0,9) # checkcode +=str(tmp) # print(checkcode)#二般 import string # print(''.join(random.sample(string.ascii_lowercase+string.digits,5)))#隨機驗證碼可用(5位含數字和密碼) # # print(''.join(random.sample(string.ascii_lowercase+string.digits,5)))#隨機驗證碼可用(5位含數字和密碼) # # #打印a-z # print(string.ascii_lowercase)#一 # #打印A-Z # print(string.ascii_letters)#二?
os模塊
#!/usr/bin/env python # import os # # print(os.getcwd())#打印當前目錄 # 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所指向的文件或者目錄的最后修改時間#更多 #https://docs.python.org/2/library/os.html?highlight=os#module-ossys
#!/usr/bin/env python #—*—coding:utf-8—*— import sys # print(sys.argv ) #命令行參數List,第一個元素是程序本身路徑 #>>>['E:/python/day5/sys(1).py']# sys.exit(n) #退出程序,正常退出時exit(0)# print(sys.version ) #獲取Python解釋程序的版本信息 # >>>3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)]# print(sys.maxint) #最大的Int值 # sys.path #返回模塊的搜索路徑,初始化時使用PYTHONPATH環境變量的值 # print(sys.platform) #返回操作系統平臺名稱 # >>>win32# print(sys.stdout.write('please:')) # >>>please:7# val = sys.stdin.readline()[:-1]shutil
高級的文件文件夾壓縮包處理模塊
#!/usr/bin/env python import shutil# shutil.copyfileobj(fsrc, fdst[, length]) # 將文件內容拷貝到另一個文件中,可以部分內容# shutil.copyfile(src, dst) # 拷貝文件# shutil.copymode(src, dst) # 僅拷貝權限。內容、組、用戶均不變# shutil.copystat(src, dst) # 拷貝狀態的信息,包括:mode bits, atime, mtime, flags# shutil.copy(src, dst) # 拷貝文件和權限# shutil.copy2(src, dst) # 拷貝文件和狀態信息# shutil.rmtree(path[, ignore_errors[, onerror]]) # 遞歸的去刪除文件# shutil.move(src, dst) # 遞歸的去移動文件(刪除文件) shutil.make_archive(base_name, format,...)創建壓縮包并返回文件路徑,例如:zip、tarbase_name: 壓縮包的文件名,也可以是壓縮包的路徑。只是文件名時,則保存至當前目錄,否則保存至指定路徑, 如:www =>保存至當前路徑 如:/Users/wupeiqi/www =>保存至/Users/wupeiqi/ format: 壓縮包種類,“zip”, “tar”, “bztar”,“gztar” root_dir: 要壓縮的文件夾路徑(默認當前目錄) owner: 用戶,默認當前用戶 group: 組,默認當前組 logger: 用于記錄日志,通常是logging.Logger對象#將 /Users/wupeiqi/Downloads/test 下的文件打包放置當前程序目錄import shutil ret = shutil.make_archive("wwwwwwwwww", 'gztar', root_dir='/Users/wupeiqi/Downloads/test')#將 /Users/wupeiqi/Downloads/test 下的文件打包放置 /Users/wupeiqi/目錄 import shutil ret = shutil.make_archive("/Users/wupeiqi/wwwwwwwwww", 'gztar', root_dir='/Users/wupeiqi/Downloads/test')shelve
import shelve d = shelve.open('shelve_test')#寫 # name = {"zhangsan","lisi","wanger"} # info = {"age":25,"job":"IT"} # d["name"] = name # d["info"] = info # d.close() #寫完以后,它會自動生成三個文件,.dat和.dir和.bak#讀 # print(d.get("name")) # print(d.get("info"))xml處理
<?xml version="1.0"?> <data><country name="Liechtenstein"><rank updated="yes">2</rank><year>2008</year><gdppc>141100</gdppc><neighbor name="Austria" direction="E"/><neighbor name="Switzerland" direction="W"/></country><country name="Singapore"><rank updated="yes">5</rank><year>2011</year><gdppc>59900</gdppc><neighbor name="Malaysia" direction="N"/></country><country name="Panama"><rank updated="yes">69</rank><year>2011</year><gdppc>13600</gdppc><neighbor name="Costa Rica" direction="W"/><neighbor name="Colombia" direction="E"/></country> </data> #!/usr/bin/env python #—*—coding:utf-8—*—import xml.etree.ElementTree as ETtree = ET.parse("xml.xml") root = tree.getroot() print(root.tag)#遍歷xml文檔 # for child in root: # print(child.tag, child.attrib) # for i in child: # print(i.tag,i.text) #>>>結果 # data # country {'name': 'Liechtenstein'} # rank 2 # year 2008 # gdppc 141100 # neighbor None # neighbor None # country {'name': 'Singapore'} # rank 5 # year 2011 # gdppc 59900 # neighbor None # country {'name': 'Panama'} # rank 69 # year 2011 # gdppc 13600 # neighbor None # neighbor None#只遍歷year 節點 # for node in root.iter('year'): # print(node.tag,node.text) # >>>data # >>>year2008 # >>>year 2011 # >>>year 2011yaml處理
? yaml和xml差不多,只是需要自己安裝一個模塊,參考鏈接:參考文檔:http://pyyaml.org/wiki/PyYAMLDocumentation?
configparser
這個比較好玩一點,大家多知道mysql配置文件my.cnf文件的格式吧
#類似這樣的配置文件,一塊一塊的分類 [DEFAULT] ServerAliveInterval = 45 Compression = yes CompressionLevel = 9 ForwardX11 = yes[bitbucket.org] User = hg[topsecret.server.com] Port = 50022 ForwardX11 = no #生成類似格式的文件 import configparserconfig = 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' # mutates the parser topsecret['ForwardX11'] = 'no' # same here config['DEFAULT']['ForwardX11'] = 'yes' with open('example.ini', 'w') as configfile:config.write(configfile) #讀 # import configparser # config = configparser.ConfigParser() # config.sections() # # config.read('example.ini') # # print(config.defaults()) # >>>OrderedDict([('compressionlevel', '9'), ('compression', 'yes'), ('serveraliveinterval', '45'), ('forwardx11', 'yes')]) # print(config['bitbucket.org']["User"]) # >>>hg # print(config["topsecret.server.com"]["host port"]) # 50022 #刪除(創建一個新文件,并刪除bitbucket.org) import configparser config = configparser.ConfigParser() config.sections()config.read('example.ini') rec = config.remove_section("bitbucket.org")#刪除該項 config.write(open("example.cfg","w")) [DEFAULT] compressionlevel = 9 compression = yes serveraliveinterval = 45 forwardx11 = yes[topsecret.server.com] host port = 50022 forwardx11 = nohashlib
用于加密相關的操作
#!/usr/bin/env pythonimport hashlib# m = hashlib.md5() # m.update(b"hello") # print(m.digest())#進行二進制加密 # print(len(m.hexdigest())) #16進制長度 # print(m.hexdigest())#16進制格式hash # >>>b']A@*\xbcK*v\xb9q\x9d\x91\x10\x17\xc5\x92' # >>>32 # >>>5d41402abc4b2a76b9719d911017c592#md5 # hash = hashlib.md5() # hash.update(('admin').encode()) # print(hash.hexdigest()) # >>>21232f297a57a5a743894a0e4a801fc3#sha1 # hash = hashlib.sha1() # hash.update(('admin').encode()) # print(hash.hexdigest()) # >>>d033e22ae348aeb5660fc2140aec35850c4da997#sha256 # hash = hashlib.sha256() # hash.update(('admin').encode()) # print(hash.hexdigest()) # >>>8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918#sha384 # hash = hashlib.sha384() # hash.update(('admin').encode()) # print(hash.hexdigest()) # >>>9ca694a90285c034432c9550421b7b9dbd5c0f4b6673f05f6dbce58052ba20e4248041956ee8c9a2ec9f10290cdc0782#sha512 # hash = hashlib.sha512() # hash.update(('admin').encode()) # print(hash.hexdigest()) # >>>c7ad44cbad762a5da0a452f9e854fdc1e0e7a52a38015f23f3eab1d80b931dd472634dfac71cd34ebc35d16ab7fb8a90c81f975113d6c7538dc69dd8de9077ec#更吊的 import hmac h = hmac.new(('wueiqi').encode()) h.update(('hellowo').encode()) print(h.hexdigest()) #更多關于md5,sha1,sha256等介紹的文章看這里https://www.tbs-certificates.co.uk/FAQ/en/sha256.htmlsubprocess
調用shell
對os.system
os.spawm的替換
?
?
logging模塊簡介
Python的logging模塊提供了通用的日志系統,可以方便第三方模塊或者是應用使用。這個模塊提供不同的日志級別,并可以采用不同的方式記錄日志,比如文件,HTTP GET/POST,SMTP,Socket等,甚至可以自己實現具體的日志記錄方式。
logging模塊與log4j的機制是一樣的,只是具體的實現細節不同。模塊提供logger,handler,filter,formatter。
- logger:提供日志接口,供應用代碼使用。logger最長用的操作有兩類:配置和發送日志消息。可以通過logging.getLogger(name)獲取logger對象,如果不指定name則返回root對象,多次使用相同的name調用getLogger方法返回同一個logger對象。
- handler:將日志記錄(log record)發送到合適的目的地(destination),比如文件,socket等。一個logger對象可以通過addHandler方法添加0到多個handler,每個handler又可以定義不同日志級別,以實現日志分級過濾顯示。
- ?filter:提供一種優雅的方式決定一個日志記錄是否發送到handler。
- ?formatter:指定日志記錄輸出的具體格式。formatter的構造方法需要兩個參數:消息的格式字符串和日期字符串,這兩個參數都是可選的。
與log4j類似,logger,handler和日志消息的調用可以有具體的日志級別(Level),只有在日志消息的級別大于logger和handler的級別。
logging用法解析
1. 初始化 logger = logging.getLogger("endlesscode"),getLogger()方法后面最好加上所要日志記錄的模塊名字,后面的日志格式中的%(name)s 對應的是這里的模塊名字
2. 設置級別 logger.setLevel(logging.DEBUG),Logging中有NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL這幾種級別,日志會記錄設置級別以上的日志
3. Handler,常用的是StreamHandler和FileHandler,windows下你可以簡單理解為一個是console和文件日志,一個打印在CMD窗口上,一個記錄在一個文件上
4. formatter,定義了最終log信息的順序,結構和內容,我喜歡用這樣的格式 '[%(asctime)s] [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S',
%(name)s Logger的名字
%(levelname)s 文本形式的日志級別
%(message)s 用戶輸出的消息
%(asctime)s 字符串形式的當前時間。默認格式是 “2003-07-08 16:49:45,896”。逗號后面的是毫秒
%(levelno)s 數字形式的日志級別
%(pathname)s 調用日志輸出函數的模塊的完整路徑名,可能沒有
%(filename)s 調用日志輸出函數的模塊的文件名
%(module)s? 調用日志輸出函數的模塊名
%(funcName)s 調用日志輸出函數的函數名
%(lineno)d 調用日志輸出函數的語句所在的代碼行
%(created)f 當前時間,用UNIX標準的表示時間的浮 點數表示
%(relativeCreated)d 輸出日志信息時的,自Logger創建以 來的毫秒數
%(thread)d 線程ID。可能沒有
%(threadName)s 線程名。可能沒有
%(process)d 進程ID。可能沒有
5. 記錄 使用object.debug(message)來記錄日志
下面來寫一個實例,在CMD窗口上只打出error以上級別的日志,但是在日志中打出debug以上的信息
運行一下將會看到CMD窗口只記錄兩條,spam.log中記錄了五條日志
除了這些基本用法,還有一些常見的小技巧可以分享一下。
# 格式化輸出 service_name = "Booking" logger.error('%s service is down!' % service_name) # 使用python自帶的字符串格式化,不推薦 logger.error('%s service is down!', service_name) # 使用logger的格式化,推薦 logger.error('%s service is %s!', service_name, 'down') # 多參數格式化 logger.error('{} service is {}'.format(service_name, 'down')) # 使用format函數,推薦# 2016-10-08 21:59:19,493 ERROR : Booking service is down!記錄異常信息
當你使用logging模塊記錄異常信息時,不需要傳入該異常對象,只要你直接調用logger.error()?或者?logger.exception()就可以將當前異常記錄下來。
# 記錄異常信息try:1 / 0 except:# 等同于error級別,但是會額外記錄當前拋出的異常堆棧信息logger.exception('this is an exception message')# 2016-10-08 21:59:19,493 ERROR : this is an exception message # Traceback (most recent call last): # File "D:/Git/py_labs/demo/use_logging.py", line 45, in # 1 / 0 # ZeroDivisionError: integer division or modulo by zerologging配置要點
GetLogger()方法
這是最基本的入口,該方法參數可以為空,默認的logger名稱是root,如果在同一個程序中一直都使用同名的logger,其實會拿到同一個實例,使用這個技巧就可以跨模塊調用同樣的logger來記錄日志。
另外你也可以通過日志名稱來區分同一程序的不同模塊,比如這個例子。
logger = logging.getLogger("App.UI") logger = logging.getLogger("App.Service")Formatter日志格式
Formatter對象定義了log信息的結構和內容,構造時需要帶兩個參數:
- 一個是格式化的模板fmt,默認會包含最基本的level和?message信息
- 一個是格式化的時間樣式datefmt,默認為?2003-07-08 16:49:45,896 (%Y-%m-%d %H:%M:%S)
fmt中允許使用的變量可以參考下表。
- %(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?用戶輸出的消息
SetLevel 日志級別
Logging有如下級別: DEBUG,INFO,WARNING,ERROR,CRITICAL
默認級別是WARNING,logging模塊只會輸出指定level以上的log。這樣的好處, 就是在項目開發時debug用的log,在產品release階段不用一一注釋,只需要調整logger的級別就可以了,很方便。
Handler 日志處理器
最常用的是StreamHandler和FileHandler, Handler用于向不同的輸出端打log。
Logging包含很多handler, 可能用到的有下面幾種
- StreamHandler?instances send error messages to streams (file-like objects).
- FileHandler?instances send error messages to disk files.
- RotatingFileHandler?instances send error messages to disk files, with support for maximum log file sizes and log file rotation.
- TimedRotatingFileHandler?instances send error messages to disk files, rotating the log file at certain timed intervals.
- SocketHandler?instances send error messages to TCP/IP sockets.
- DatagramHandler?instances send error messages to UDP sockets.
- SMTPHandler?instances send error messages to a designated email address.
Configuration 配置方法
logging的配置大致有下面幾種方式。
logging.basicConfig
basicConfig()提供了非常便捷的方式讓你配置logging模塊并馬上開始使用,可以參考下面的例子。具體可以配置的項目請查閱官方文檔。
import logginglogging.basicConfig(filename='example.log',level=logging.DEBUG) logging.debug('This message should go to the log file')logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) logging.debug('This message should appear on the console')logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') logging.warning('is when this event was logged.')備注: 其實你甚至可以什么都不配置直接使用默認值在控制臺中打log,用這樣的方式替換print語句對日后項目維護會有很大幫助。
通過文件配置logging
如果你希望通過配置文件來管理logging,可以參考這個官方文檔。在log4net或者log4j中這是很常見的方式。
# logging.conf [loggers] keys=root[logger_root] level=DEBUG handlers=consoleHandler #,timedRotateFileHandler,errorTimedRotateFileHandler################################################# [handlers] keys=consoleHandler,timedRotateFileHandler,errorTimedRotateFileHandler[handler_consoleHandler] class=StreamHandler level=DEBUG formatter=simpleFormatter args=(sys.stdout,)[handler_timedRotateFileHandler] class=handlers.TimedRotatingFileHandler level=DEBUG formatter=simpleFormatter args=('debug.log', 'H')[handler_errorTimedRotateFileHandler] class=handlers.TimedRotatingFileHandler level=WARN formatter=simpleFormatter args=('error.log', 'H')################################################# [formatters] keys=simpleFormatter, multiLineFormatter[formatter_simpleFormatter] format= %(levelname)s %(threadName)s %(asctime)s: %(message)s datefmt=%H:%M:%S[formatter_multiLineFormatter] format= ------------------------- %(levelname)s -------------------------Time: %(asctime)sThread: %(threadName)sFile: %(filename)s(line %(lineno)d)Message:%(message)sdatefmt=%Y-%m-%d %H:%M:%S假設以上的配置文件放在和模塊相同的目錄,代碼中的調用如下。
import os filepath = os.path.join(os.path.dirname(__file__), 'logging.conf') logging.config.fileConfig(filepath) return logging.getLogger()?
?
Python之re模塊 —— 正則表達式操作[原創]
?
這個模塊提供了與 Perl 相似l的正則表達式匹配操作。Unicode字符串也同樣適用。
?
正則表達式使用反斜杠" \ "來代表特殊形式或用作轉義字符,這里跟Python的語法沖突,因此,Python用"?\\\\?"表示正則表達式中的" \ ",因為正則表達式中如果要匹配" \ ",需要用\來轉義,變成" \\ ",而Python語法中又需要對字符串中每一個\進行轉義,所以就變成了"?\\\\?"。
?
上面的寫法是不是覺得很麻煩,為了使正則表達式具有更好的可讀性,Python特別設計了原始字符串(raw string),需要提醒你的是,在寫文件路徑的時候就不要使用raw string了,這里存在陷阱。raw string就是用'r'作為字符串的前綴,如 r"\n":表示兩個字符"\"和"n",而不是換行符了。Python中寫正則表達式時推薦使用這種形式。
?
絕大多數正則表達式操作與 模塊級函數或RegexObject方法 一樣都能達到同樣的目的。而且不需要你一開始就編譯正則表達式對象,但是不能使用一些實用的微調參數。
?
1.正則表達式語法
??????? 為了節省篇幅,這里不再敘述了。
?
2.martch和search的區別
??????? Python提供了兩種不同的原始操作:match和search。match是從字符串的起點開始做匹配,而search(perl默認)是從字符串做任意匹配。
?
??????? 注意:當正則表達式是' ^ '開頭時,match與search是相同的。match只有當且僅當被匹配的字符串開頭就能匹配 或 從pos參數的位置開始就能匹配 時才會成功。如下:
>>> import re?
>>> re.match("c", "abcdef")?
>>> re.search("c","abcdef")?
<_sre.SRE_Match object at 0x00A9A988>
>>> re.match("c", "cabcdef")?
<_sre.SRE_Match object at 0x00A9AB80>
>>> re.search("c","cabcdef")?
<_sre.SRE_Match object at 0x00AF1720>
>>> patterm = re.compile("c")?
>>> patterm.match("abcdef")?
>>> patterm.match("abcdef",1)?
>>> patterm.match("abcdef",2)?
<_sre.SRE_Match object at 0x00A9AB80>
3.模塊內容
re.compile(pattern, flags=0)
?
編譯正則表達式,返回RegexObject對象,然后可以通過RegexObject對象調用match()和search()方法。
?
prog = re.compile(pattern)
result = prog.match(string)
跟
result = re.match(pattern, string)
是等價的。
?
第一種方式能實現正則表達式的重用。
?
re.search(pattern, string, flags=0)
?
在字符串中查找,是否能匹配正則表達式。返回_sre.SRE_Match對象,如果不能匹配返回None。
?
re.match(pattern, string, flags=0)
?
字符串的開頭是否能匹配正則表達式。返回_sre.SRE_Match對象,如果不能匹配返回None。
?
re.split(pattern, string, maxsplit=0)
?
通過正則表達式將字符串分離。如果用括號將正則表達式括起來,那么匹配的字符串也會被列入到list中返回。maxsplit是分離的次數,maxsplit=1分離一次,默認為0,不限制次數。
>>> re.split('\W+', 'Words, words, words.')?
['Words', 'words', 'words', '']?
>>> re.split('(\W+)', 'Words, words, words.')?
['Words', ', ', 'words', ', ', 'words', '.', '']?
>>> re.split('\W+', 'Words, words, words.', 1)?
['Words', 'words, words.']?
>>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE)
?
注意:我使用的Python是2.6,查看源代碼發現split()并沒有flags的參數,2.7才增加。這種問題我發現不止一次了,官方的文檔 跟 源碼不一致的現象,如果發現異常,應該去源碼中找找原因。
?
如果在字符串的開始或結尾就匹配,返回的list將會以空串開始或結尾。
>>> re.split('(\W+)', '...words, words...')?
['', '...', 'words', ', ', 'words', '...', '']
?
如果字符串不能匹配,將會返回整個字符串的list。
>>> re.split("a","bbb")?
['bbb']
?
re.findall(pattern, string, flags=0)
?
找到 RE 匹配的所有子串,并把它們作為一個列表返回。這個匹配是從左到右有序地返回。如果無匹配,返回空列表。
>>> re.findall("a","bcdef")?
[]
>>> re.findall(r"\d+","12a32bc43jf3")?
['12', '32', '43', '3']
?
re.finditer(pattern, string, flags=0)
?
找到 RE 匹配的所有子串,并把它們作為一個迭代器返回。這個匹配是從左到右有序地返回。如果無匹配,返回空列表。
>>> it = re.finditer(r"\d+","12a32bc43jf3")?
>>> for match in it:?
????????????? print match.group()
12?
32?
43?
3
?
re.sub(pattern, repl, string, count=0, flags=0)
?
找到 RE 匹配的所有子串,并將其用一個不同的字符串替換。可選參數 count 是模式匹配後替換的最大次數;count 必須是非負整數。缺省值是 0 表示替換所有的匹配。如果無匹配,字符串將會無改變地返回。
?
re.subn(pattern, repl, string, count=0, flags=0)
?
與re.sub方法作用一樣,但返回的是包含新字符串和替換執行次數的兩元組。
?
re.escape(string)
?
對字符串中的非字母數字進行轉義
?
re.purge()
?
清空緩存中的正則表達式
?
4.正則表達式對象
?
re.RegexObject
?
re.compile()返回RegexObject對象
?
re.MatchObject
?
group()返回被 RE 匹配的字符串
start()返回匹配開始的位置
end()返回匹配結束的位置
span()返回一個元組包含匹配 (開始,結束) 的位置
?
5.編譯標志
編譯標志讓你可以修改正則表達式的一些運行方式。在 re 模塊中標志可以使用兩個名字,一個是全名如 IGNORECASE,一個是縮寫,一字母形式如 I。(如果你熟悉 Perl 的模式修改,一字母形式使用同樣的字母;例如 re.VERBOSE的縮寫形式是 re.X。)多個標志可以通過按位 OR-ing 它們來指定。如 re.I | re.M 被設置成 I 和 M 標志:
I?
IGNORECASE
使匹配對大小寫不敏感;字符類和字符串匹配字母時忽略大小寫。舉個例子,[A-Z]也可以匹配小寫字母,Spam 可以匹配 "Spam", "spam", 或 "spAM"。這個小寫字母并不考慮當前位置。
L?
LOCALE
影響 "w, "W, "b, 和 "B,這取決于當前的本地化設置。
locales 是 C 語言庫中的一項功能,是用來為需要考慮不同語言的編程提供幫助的。舉個例子,如果你正在處理法文文本,你想用 "w+ 來匹配文字,但 "w 只匹配字符類 [A-Za-z];它并不能匹配 "é" 或 "?"。如果你的系統配置適當且本地化設置為法語,那么內部的 C 函數將告訴程序 "é" 也應該被認為是一個字母。當在編譯正則表達式時使用 LOCALE 標志會得到用這些 C 函數來處理 "w 後的編譯對象;這會更慢,但也會象你希望的那樣可以用 "w+ 來匹配法文文本。
M?
MULTILINE
(此時 ^ 和 $ 不會被解釋; 它們將在 4.1 節被介紹.)
使用 "^" 只匹配字符串的開始,而 $ 則只匹配字符串的結尾和直接在換行前(如果有的話)的字符串結尾。當本標志指定後, "^" 匹配字符串的開始和字符串中每行的開始。同樣的, $ 元字符匹配字符串結尾和字符串中每行的結尾(直接在每個換行之前)。
S?
DOTALL
使 "." 特殊字符完全匹配任何字符,包括換行;沒有這個標志, "." 匹配除了換行外的任何字符。
X?
VERBOSE
該標志通過給予你更靈活的格式以便你將正則表達式寫得更易于理解。當該標志被指定時,在 RE 字符串中的空白符被忽略,除非該空白符在字符類中或在反斜杠之後;這可以讓你更清晰地組織和縮進 RE。它也可以允許你將注釋寫入 RE,這些注釋會被引擎忽略;注釋用 "#"號 來標識,不過該符號不能在字符串或反斜杠之後。
?
最后:如果能用字符串的方法,就不要選擇正則表達式,因為字符串方法更簡單快速。
?
轉載于:https://www.cnblogs.com/pythonwang/p/8261773.html
總結
- 上一篇: 【Machine Learning】决策
- 下一篇: 有多少种方法能把足球移出边界 Out o